martes, 9 de diciembre de 2014

Introducción a WCF - Fase I

En el siguiente articulo estaré hablando acerca de WCF - Windows Communication Foundation, considerando que este apartado es la parte introductoria al tema de servicios:

Requisitos:
Visual Studio 2012.
Lenguaje de programación C#.

¿Qué es WCF?
Revisando en la internet se encontrará mucha información al respeto, en resumen es uno o más de las apliaciones que se implementa a una solución o proyecto, con la única diferencia que son aplicaciones orientadas a servicios - SOA, para aclarar, esto no quiere decir que el concepto que planteo es solamente crear una aplicación, iré aclarando ciertos puntos que debemos considerar al momento de construir nuestra aplicación. Para mayor detalles pueden revisar el enlace más sobre WCF con respecto a SOA estaré publicando más adelante en otro articulo.

Iniciamos con la creación de nuestra solución DemoWCF:



Crear la solución haciendo referencia al .NET Framework 4.5


Solución creado DemoWCF

Creamos nuestro proyecto PyDemoWCF:


IService1.cs: Clase que invoca todos los métodos (operaciones) creados que retornaran algún valor sin importar el tipo de dato que contiene la clase Service1.cs.
// NOTA: puede usar el comando "Rename" del menú "Refactorizar" para cambiar el nombre de interfaz "IService1" en el código y en el archivo de configuración a la vez.
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value); 
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite); 
// TODO: agregue aquí sus operaciones de servicio
}
El atributo [ServiceContract] se encarga de definir lo que puede realizar un servicio. Además se encarga de describir las operaciones que ofrece un servicio. Es decir, permite la interoperabilidad (operaciones que se puede realizar) de un servicio, intercambiando información entre 2 o más SISTEMAS de una entidad local o varias entidades sin considerar el lugar en la que se encuentre. La importancia de ServiceContract se resume en un ACUERDO entre las partes involucradas que nos permiten definir la manera en que se trabaja entre las partes involucradas. Considerano los tipos de funciones que se van a ofrecer, qué tipos de datos serán enviados o recibidos y el cómo serán enviados.

El atributo [OperationContract] sirve para poder identificar las operaciones (métodos) que se esta realizando, así que, toda operación debe estar en el [ServiceContract] ya que nos permite indicar el acuerdo entre sí. 

// Utilice un contrato de datos, como se ilustra en el ejemplo siguiente, para agregar tipos compuestos a las operaciones de servicio.
// Puede agregar archivos XSD al proyecto. Después de compilar el proyecto, puede usar directamente los tipos de datos definidos aquí, con el espacio de nombres "PyDemoWCF.ContractType".
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello "; 
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
} 
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
}
Además por defecto trae implementado el [DataContract] que se encarga de describir la estructura de datos manejados por la operaciones de los servicios con sus respectivos miembros de datos [DataMember]. Considerando que omitir un miembro de datos serializados, establezca la propiedad EmitDefaultValue del atributo DataMemberAttribute en false (el valor predeterminado es true), en otro articulo se profundizará el tema de los [DataMember].

Service1.cs Clase que concreta la ejecución de todos los métodos (operaciones) creados. Es decir en esta clase es la que se implementa todas la operaciones sea cual sea, traerdatosempleado, procesarsuelto, procesarnota, etc.

// NOTA: puede usar el comando "Rename" del menú "Refactorizar" para cambiar el nombre de clase "Service1" en el código y en el archivo de configuración a la vez.
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
} 
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
App.config Archivo que contiene la configuración de nuestro servicio. El punto a resaltar por ahora sera el endpoint. Una aplicación basado en servicio siempre tiene que tener al menos un endpoint, que se encarga de seleccionar la dirección o ruta, quiere decir es fundamental en determinar una ruta para el compartimiento de información entre las parte involucradas en un servicio. Es decir el cliente involucrado debe saber la ruta para poder acceder a dicho servicio.

En nuestro caso esta por defecto la ruta localhost, pero no es solamente este punto de los endpoint's a considerar, hay muchos puntos que más adelante hablaremos sobre las https, las adrresses el tcp y behavior, entre otros.

Testeando nuestro Servicio:
// Aquí implementamos un contrato de datos
[DataContract]
public class OperacionPromedio
{
string alumno;
string especialidad;
string curso;
decimal promedio; 
[DataMember]
public string Alumno{
get { return alumno; }
set { alumno = value; } 
} 
[DataMember]
public string Especialidad
{
get { return especialidad; }
set { especialidad = value; } 
} 
[DataMember]
public string Curso { 
get { return curso; }
set { curso = value; }
} 
[DataMember]
public decimal Promedio
{
get { return promedio; }
set { promedio = value; }
}
}

// Aca invocamos a nuestro método GetNombresApellidos...
[OperationContract]
string GetNombresApellidos(string nombres, string apellidos); 
[OperationContract]
OperacionPromedio GetOperacionPromedio(int pNota1, int pNota2, int pNota3);
//Aca implementaremos un metodo que retorna un valor en cadena
public string GetNombresApellidos(string nombres, string apellidos) {
string dato;
dato = nombres +" "+ apellidos;
return dato;
} 

//Aca implementaremos un metodo que retorna valores utilizando un contrato de datos
public OperacionPromedio GetOperacionPromedio(int pNota1, int pNota2, int pNota3)
{
OperacionPromedio OpePromedio = new OperacionPromedio(); 
OpePromedio.Alumno = "Hadson Paredes Córdova";
OpePromedio.Especialidad = "Computación e Informatica";
OpePromedio.Curso = "Análisis y diseño de procesos";
OpePromedio.Promedio = Decimal.Round((pNota1 + pNota2 + pNota3) / 3, 2); 
return OpePromedio;
}
Crear el proyecto PyDemoWCF de tipo Biblioteca de servicios WCF haciendo referencia al .NET Framework 4.5


En nuestro proyecto creado explicaremos las consideraciones y conceptos de un servicio


Comencemos:

Al crear nuestro proyecto de tipo Biblioteca de servicios WCF por defecto crea los siguientes archivos con los siguientes contenidos.



Todas las operaciones que fueron invocados en la clase IService1.cs es decir string GetData(int value) y CompositeType GetDataUsingDataContract(CompositeType composite) ahora se podrá visualizar la estructura de dichos eventos:


Ambos métodos (Operaciones) son dependientes de la clase IService1 y son de tipo publico.

Para poder realizar el Test de nuestra aplicación implementaré 2 nuevos métodos independientemente de las que comentamos (las que trae por defecto al crear el proyecto Biblioteca de servicios WCF) líneas arriba.

En la clase IService1.cs implementamos un contrato de datos:

Además en el [ServiceContract] hacemos referencia a los métodos GetNombresApellidos y GetOperacionPromedio:

Ahora bien en la clase Service1.cs implementamos  nuestro métodos:

Ahora iniciamos nuestro test, como se vera en la siguiente imagen de la ficha con formato:


Para nuestro test solo se va a realizar con e método GetOperacionPromedio. El cliente de prueba nos ofrece la sección de métodos, solicitud y respuesta.

De la misma manera nos ofrece en la ficha XML con la única diferencia que son datos en XML:



Ahora ingresamos los datos respectivos en la sección de Solicitud:




Y como se visualiza en la sección de respuesta son los valores que se esta retornando según la operación o método en ejecución, que en lo interno se llega a ejecutar. Es decir si el cliente nos envía una solicitud o petición de algo, el servicio siempre tiene que devolver un valor o respuesta verídica o una excepción o error tiene mucho que ver la implementación que se desarrollo. Quiere decir para toda solicitud o petición  habrá algo que devolver o responder.

Veámoslo a motodo XLM:

De esta manera es la que el cliente podrá leer la respuesta de su solicitud.

Bueno, espero que les aya gustado en el siguiente articulo se implementara el proyecto. El como se consume la información a lado del cliente de nuestro servicio creado. Nos vemos...!!!

sábado, 1 de noviembre de 2014

sábado, 2 de agosto de 2014

Implementar SyntaxHighlighter en Blogger

En el siguiente apartado implementaremos SyntaxHighlighter en su versión online. Permitiendo resaltar el texto del algunos lenguajes de programación, para mayor detalle puedes visitar la pagina oficial.

Ingresamos a nuestro blog y nos ubicamos en la opción de Plantillas y clic en Editar HTML:

Nos ubicamos en la sección de <head></head> del HTML de la plantilla y pegamos las siguientes lineas:








Y clic en guardar plantilla:

Para poder hacer efecto de las lineas adicionadas, debemos considerar al momento de redactar un nuevo tema debemos adicionar <pre class="brush: csharp"> aquí el código que quieras publicar, en mi caso es C# </pre> estando en la versión HTML:

Y como resultado tendríamos lo siguiente:
Todo esto recién se notará cuando se realiza la publicación

Lenguajes que soporta:

  • AppleScript (applescript)
  • ActionScript3 (as3, actionscript3)
  • Bash/shell (bash, shell)
  • ColdFusion (cf, coldfusion)
  • C# (c-sharp, csharp)
  • C++ (cpp, c)
  • CSS (css)
  • Delphi (delphi, pas, pascal)
  • Diff (diff, patch)
  • Erlang (erl, erlang)
  • Groovy (groovy)
  • JavaScript (js, jscript, javascript)
  • Java (java)
  • JavaFX (jfx, javafx)
  • objc Objective-C/Cocoa
  • Perl (perl, pl)
  • PHP (php)
  • Plain Text (plain, text)
  • PowerShell (ps, powershell)
  • Python (py, python)
  • Ruby (rails, ror, ruby)
  • Sass (sass,scss)
  • Scala (scala)
  • SQL (sql)
  • Visual Basic (vb, vbnet)
  • XML (xml, xhtml, xslt, html, xhtml
Espero los sirva, nos vemos en el siguiente tutorial.

viernes, 18 de abril de 2014

Mensaje personalizado T-SQL

En este apartado pretendemos demostrar con un ejemplo claro y sencillo el uso y control de Mensaje personalizado usando T-SQL:

Requisitos :
1. SQL Server 2008 -  (T-SQL).

ESTRUCTURA DE TABLA
Antes de iniciar el desarrollo de T-SQL, considero que ya cuento con la tabla creada. Para este caso estoy tomando como referencia la tabla SEG_MODULO del diseño y modelo de base de datos Modulo de Control de Acceso - Primera Fase http://hadsonpar.blogspot.com/2013/08/modulo-de-control-de-acceso-primera-fase.html

Iniciamos creando nuestro procedimiento almacenado SP_INS_SEG_MODULO, haciendo referencia a la estructura de tabla:
CREATE PROCEDURE [dbo].[SP_INS_SEG_MODULO]  
 @PC_DES_MODULO  VARCHAR(50),
 @PC_MAN_MODULO  VARCHAR(200),
 @PC_USU_CREADOR  VARCHAR(20),
 @PF_FEC_CREACION DATETIME,
 @PC_USU_ACTUALIZA VARCHAR(20),
 @PF_FEC_ACTUALIZA DATETIME,
 @OC_MESSAGE_ERROR VARCHAR(1000) OUTPUT   
AS 
BEGIN TRY
 DECLARE @PC_ID_MODULO INTEGER
  
 EXEC SP_GEN_ID_MODULO
   @PC_ID_MODULO OUTPUT
  
  INSERT INTO SEG_MODULO
     (ID_MODULO, 
     DES_MODULO, 
     MAN_MODULO, 
     USU_CREADOR, 
     FEC_CREACION,
     USU_ACTUALIZA,
     FEC_ACTUALIZA)
  VALUES (@PC_ID_MODULO, 
     @PC_DES_MODULO, 
     @PC_MAN_MODULO, 
     @PC_USU_CREADOR, 
     @PF_FEC_CREACION,     
     @PC_USU_ACTUALIZA,
     @PF_FEC_ACTUALIZA)
  SELECT @OC_MESSAGE_ERROR = ''
END TRY
BEGIN CATCH
 SELECT @OC_MESSAGE_ERROR =  (SELECT 'ERROR EN PROCEDIMIENTO : '+ ERROR_PROCEDURE() + ' , MENSAJE : ' + ERROR_MESSAGE() + ' ID_MODULO : ' + CONVERT(VARCHAR,@PC_ID_MODULO))  
END CATCH

El parámetro @OC_MESSAGE_ERROR es solo de salida, parámetro que nos ayudará a controlar los mensajes personalizadosAhora ingresamos el valor que nos solicita cada parámetro de nuestro procedimiento almacenado que acabamos de crear.

INGRESAR PARÁMETROS Y ACEPTAR
Script que genera al ejecutar el procedimiento almacenado.

USE [BD_SISTEMA]
GO

DECLARE @return_value int,
  @OC_MESSAGE_ERROR varchar(1000)

EXEC @return_value = [dbo].[SP_INS_SEG_MODULO]
  @PC_DES_MODULO = N'SISTEMA',
  @PC_MAN_MODULO = N'\\192.168.1.113\Sistemas\ManualModulo',
  @PC_USU_CREADOR = N'HADSON',
  @PF_FEC_CREACION = N'18/04/2014',
  @PC_USU_ACTUALIZA = N'HADSON',
  @PF_FEC_ACTUALIZA = N'18/04/2014',
  @OC_MESSAGE_ERROR = @OC_MESSAGE_ERROR OUTPUT

SELECT @OC_MESSAGE_ERROR as N'@OC_MESSAGE_ERROR'

SELECT 'Return Value' = @return_value

GO 
RESULTADOS
CONFIRMAMOS SI LOS DATOS FUERON EFECTIVAMENTE INGRESADOS
Por ahora todo bien y que pasa si es que comenzamos a ingresar un nuevo registro sin ninguna validación que pueda restringir dicho proceso de inserción. 

Ejemplo
En nuestra primera inserción ingresamos como descripción de modulo SISTEMA y si intentamos ingresar nuevamente SISTEMA se insertará sin ningún problema, generando ambigüedad de datos, ejecutamos en Script generado de nuestro procedimiento almacenado.
COMO RESULTADO AHORA CONTAMOS CON 2 REGISTRO DE LA MISMA DESCRIPCIÓN DE MODULO
Para poder controlar nuestra validación de inserción de datos utilizaremos en este caso un mensaje personalizado y en otro ejemplo utilizaremos la función RAISERROR de T-SQL. Para ambos casos el programador es quien lo define.

Ejemplo con mensaje personalizado:


ALTER PROCEDURE [dbo].[SP_INS_SEG_MODULO]  
 @PC_DES_MODULO  VARCHAR(50),
 @PC_MAN_MODULO  VARCHAR(200),
 @PC_USU_CREADOR  VARCHAR(20),
 @PF_FEC_CREACION DATETIME,
 @PC_USU_ACTUALIZA VARCHAR(20),
 @PF_FEC_ACTUALIZA DATETIME,
 @OC_MESSAGE_ERROR VARCHAR(1000) OUTPUT   
AS 
BEGIN TRY
 DECLARE @PC_ID_MODULO INTEGER
 
 IF EXISTS (SELECT * FROM SEG_MODULO
    WHERE DES_MODULO = LTRIM(RTRIM(@PC_DES_MODULO)))
  BEGIN
   SELECT @OC_MESSAGE_ERROR = 'LA DESCRIPCION QUE ESTA INTENTANDO REGISTRAS YA EXISTE EN LA BASE DE DATOS'
  END
 ELSE
  BEGIN
   EXEC SP_GEN_ID_MODULO
     @PC_ID_MODULO OUTPUT
    
    INSERT INTO SEG_MODULO
       (ID_MODULO, 
       DES_MODULO, 
       MAN_MODULO, 
       USU_CREADOR, 
       FEC_CREACION,
       USU_ACTUALIZA,
       FEC_ACTUALIZA)
    VALUES (@PC_ID_MODULO, 
       @PC_DES_MODULO, 
       @PC_MAN_MODULO, 
       @PC_USU_CREADOR, 
       @PF_FEC_CREACION,     
       @PC_USU_ACTUALIZA,
       @PF_FEC_ACTUALIZA)
    SELECT @OC_MESSAGE_ERROR = ''
  END
END TRY
BEGIN CATCH
 SELECT @OC_MESSAGE_ERROR =  (SELECT 'ERROR EN PROCEDIMIENTO : '+ ERROR_PROCEDURE() + ' , MENSAJE : ' + ERROR_MESSAGE() + ' ID_MODULO : ' + CONVERT(VARCHAR,@PC_ID_MODULO))  
END CATCH


COMO SE APRECIA EL PARAMETRO DE SALIDA @OC_MESSAGE_ERROR DEVUELVE EL MENSAJE PERSONALIZADO
Esto se debe a la simple y sencilla validación que se realiza y se devuelve en nuestro parámetro de salida. Línea de código que realiza esta validación.


IF EXISTS (SELECT * FROM SEG_MODULO
    WHERE DES_MODULO = LTRIM(RTRIM(@PC_DES_MODULO)))
  BEGIN
   SELECT @OC_MESSAGE_ERROR = 'LA DESCRIPCION QUE ESTA INTENTANDO REGISTRAS YA EXISTE EN LA BASE DE DATOS'
  END

Ejemplo con RAISERROR :

Para poder hacer uso de esta función propia del T-SQL modificaremos nuestro procedimiento almacenado. Donde nuestro parámetro de salida @OC_MESSAGE_ERROR, lo pasaremos como un variable para setear el mensaje o también pueden hacerlo de manera directa RAISERROR ( 'LA DESCRIPCION QUE ESTA INTENTANDO REGISTRAS YA EXISTE EN LA BASE DE DATOS',16,1)  el resultado sera lo mismo.


ALTER PROCEDURE [dbo].[SP_INS_SEG_MODULO]  
 @PC_DES_MODULO  VARCHAR(50),
 @PC_MAN_MODULO  VARCHAR(200),
 @PC_USU_CREADOR  VARCHAR(20),
 @PF_FEC_CREACION DATETIME,
 @PC_USU_ACTUALIZA VARCHAR(20),
 @PF_FEC_ACTUALIZA DATETIME  
AS 
BEGIN 
 DECLARE @PC_ID_MODULO  INTEGER
 DECLARE @OC_MESSAGE_ERROR VARCHAR(1000)
 
 IF EXISTS (SELECT * FROM SEG_MODULO
    WHERE DES_MODULO = LTRIM(RTRIM(@PC_DES_MODULO)))
  BEGIN
   SET @OC_MESSAGE_ERROR = 'LA DESCRIPCION QUE ESTA INTENTANDO REGISTRAS YA EXISTE EN LA BASE DE DATOS'
   RAISERROR (@OC_MESSAGE_ERROR,16,1)
  END
 ELSE
  BEGIN
   EXEC SP_GEN_ID_MODULO
     @PC_ID_MODULO OUTPUT
    
    INSERT INTO SEG_MODULO
       (ID_MODULO, 
       DES_MODULO, 
       MAN_MODULO, 
       USU_CREADOR, 
       FEC_CREACION,
       USU_ACTUALIZA,
       FEC_ACTUALIZA)
    VALUES (@PC_ID_MODULO, 
       @PC_DES_MODULO, 
       @PC_MAN_MODULO, 
       @PC_USU_CREADOR, 
       @PF_FEC_CREACION,     
       @PC_USU_ACTUALIZA,
       @PF_FEC_ACTUALIZA)
    SELECT @OC_MESSAGE_ERROR = ''
  END
END



RESULTADO - RAISERROR
En ambos casos la validación es a nivel de servidor de base de datos, y puedes usar varios mensaje personalizados dependiendo el desarrollo que vienes realizando.

Cambios la descripción para ver efecto de todo este proceso que acabamos de realizar.


RESULTADO: SE INGRESO LA DESCRIPCIÓN VENTA
Esto es una las formas de controlar las validaciones a nivel de servidor de base de datos, hay muchas formas más de poder hacerlo. Nos vemos en nuestro siguiente apartado.

sábado, 5 de abril de 2014

Función Decode - ORACLE

Función DECODE():
Esta función hace aveces de la sentencia CASE o IF-THEN-ELSE, para facilitar consultas condicionales. Basicamente se encarga de descifra una expresión después de compararla con cada valor de búsqueda. Considerando si la expresión es la misma que la búsqueda, se devuelve el resultado. Si se omite el valor por defecto, se devolverá un valor nulo donde una búsqueda no coincida con ninguno de los valores resultantes

Sintaxis.

DECODE(campo,valor_comprobar1,valor_devuelto_si_coincide1,valor_comprobar2,valor_devuelto_si_coincide2, ..., valor_caso_contrario_por_defecto)

Ejemplo - Caso:

Necesitamos mostrar el last_name, salary, commission_pct + salary, especificar en texto si comiciona o no, y mostrar la descripción de los departamentos solicitados 10, 20, 60, 70 y 100 entre el rango del 10 al 100, considerando que los departamentos que no se solicitan y se encuentra en dicho rango se debe especificar como - Depart. no especificado.
--Considere los comentarios
select last_name,
      salary,
      (salary * nvl(commission_pct,0))+salary add_commi_salary,
      case 
          when nvl(commission_pct,0) = 0 then 'No comisiona' else 'comisiona' end text_commi_salary,
      decode(department_id,--campo que condiciona
                           --Las busquedas de los valores resultantes que se necesita encontrar
                           10,'Administration',--si la condición es 10 = Administration,la misma función se considera con 20,60,70 y 100
                           20,'Marketing', 
                           60,'IT', 
                           70,'Public Relations',
                           100,'Finance',
                                'Depart. no especificado'--en caso no se cunpliera ninguna de las condiciones se mostrará - Depart. no especificado
                                ) as department
from  employees
where department_id between 10 and 100
order by salary;

--Si se omite el valor por defecto nos mostrara como nulos (campo - department)
select last_name,
      salary,
      (salary * nvl(commission_pct,0))+salary add_commi_salary,
      case 
          when nvl(commission_pct,0) = 0 then 'No comisiona' else 'comisiona' end text_commi_salary,
      decode(department_id,--campo que condiciona
                           --Las busquedas de los valores resultantes que se necesita encontrar
                           10,'Administration',--si la condición es 10 = Administration,la misma función se considera con 20,60,70 y 100
                           20,'Marketing', 
                           60,'IT', 
                           70,'Public Relations',
                           100,'Finance'
                                ) as department
from  employees
where department_id between 10 and 100
order by salary;
La base de datos para ejecutar los ejemplos es de RRHH oracle express.

sábado, 1 de febrero de 2014

Qué es Twitter Bootstrap

Bootstrap, es uno de los frameworks más famosos en la actualidad y uno de los más utilizados, aunque no lo sepas, muchas páginas o sitios web de las que visitamos en nuestro día a día están creados bajo este framework. Originalmente fue creado por dos desarrolladores/diseñadores de twitter para acelerar el diseño de sus aplicaciones web.

La mayor ventaja de este framework es que podemos crear interfaces que se adapten a los distintos navegadores (responsive design - diseño responsivo) apoyándonos con todo el potencial del framework ya que cuenta con distintos componentes que nos ahorrarán mucho esfuerzo y tiempo. Sus principales tecnologías son HTML, CSS y Javascript, es decir todos ellos hacen que sea simple y flexible para cualquier componente alternativo e interacciones de interfaz de usuarios finales bastante amigables y flexibles.

Principales características:
  • Ofrece una serie de plantillas CSS y ficheros JavaScript que nos permiten integrar el framework de forma sencilla y potente en cualquier tipo de proyectos webs.
  • Bueno, de lo mencionado al principio que permite crear interfaces que se adapten a los diferentes navegadores, tanto de escritorio como tablets y móviles a distintas escalas y resoluciones de pantalla.
  • Se integra perfectamente con las principales librerías JavaScript, por ejemplo JQuery.
  • Ni que decir, ofrece un diseño sólido usando estándares como CSS3/HTML5.
  • Y lo más importante de este framework, es ligero y se integra de forma limpia en cualquier tipo de proyecto web.
  • Así mismo, funciona con todos los navegadores, incluido Internet Explorer usando HTML Shim para que reconozca los tags HTML5.
  • Dispone de distintos layout predefinidos con estructuras fijas a 940 píxeles de distintas columnas o diseños.
Para concluir con este pequeño articulo:

Bootstrap es un proyecto de código abierto con licencia Apache que podemos descargar desde su pagina oficial o github. Así ver los distintos ejemplos con los que podemos trabajar en cualquier implementación web. Para mayor información puede revisar los siguientes sitios.

Sitio web | Bootstrap
Descargar | Bootstrap en GitHub
Más información | Ejemplos de Bootstrap