Sei sulla pagina 1di 38

Programacin en 3 capas

Hola a todos:

Despus de tantos meses de tener abandonado el Blog por fin hoy se libera un espacio en mi
agenda, tiempo que he decidido compartir con todos y cada uno de ustedes.

En este articulo hablare y tratare de explicar con los detalles mas mnimos que es la
arquitectura 3 capas, cuales son sus ventajas, como empezar un proyecto 3 capas, cuales son
las diferencias entre cada una de ellas, como comunicarlas y como crear un proyecto con la
arquitectura 3 capas utilizando Visual Studio 2012.

Antes de comenzar a leer este articulo recuerde que: El objetivo no es otro mas que el de
orientar a los Parvulos .Net sobre la arquitectura de software 3 capas, todo lo escrito en este
articulo no es ensayado, no es revisado por nadie mas, por lo cual podra contener errores
gramaticales y sintcticos, el articulo y sus conceptos no pretenden ser la verdad absoluta del
tema, sintase en confianza de dejar sus comentarios y opiniones en la seccin de
comentarios al final del mismo y si lo considera prudente enveme un correo electrnico por
medio del formulario de contacto y por ultimo si el articulo le es de utilidad por favor considere
dejar un comentario de agradecimiento.

Requisitos: Visual Studio 2012, Framework 4.0, el proveedor de datos SqlCompact 4.0
instalado en su equipo y muchas ganas de aprender.

Como siempre recomiendo encarecidamente que antes de descargar los proyectos de ejemplo
(que les pondr al final de articulo), traten de hacerlo ustedes mismos siguiendo paso a paso
todo lo que se mencionara aqu, si tienen dudas en uno en especifico no duden en
contactarme.

Dicho todo lo anterior, comencemos

Arquitectura 3 capas en .Net

Ha creado usted software?Si?, entonces sabe lo complejo que resulta crear rutinas y
funciones para cada uno de los formularios, importar todas las referencias del motor de base de
datos en cada uno de los formularios, cambiar cdigo aqu y all (porque tiene copias del
mismo cdigo en muchos lugares), escribir la lgica de validacin de campos dentro del evento,
corregir los bugs que pudieran presentarse y no se diga de implementarles mejoras al software,
etc., No? entonces no se preocupe este es un buen manual de como evitarse muchos dolores
de cabeza en diseo de su arquitectura a seguir.

Para que se de una mejor idea de que hablo, por favor descargue este proyecto de ejemplo,
ejectelo, analice el cdigo, observe como para realizar una misma funcionalidad en dos
lugares diferentes tuvimos que escribir casi las mismas lneas de cdigo.

A partir de aqu en adelante estaremos trabajando con el proyecto descargado, aplicndole la


Arquitectura 3 capas para demostrar como esta Arquitectura de Diseo nos ayuda a:

Separar responsabilidades, cada capa tiene una funcin especifica y no interviene con
la de las dems.

Reutilizar cdigo

La separacin de roles en tres capas, hace mas fcil reemplazar o modificar a una, sin
afectar a los mdulos restantes

El cdigo de la capa intermedia puede ser reutilizado por mltiples


Capacidad de migrar nuestro motor de Base de Datos sin grandes impactos al resto del
proyecto.

Poder cambiar el Front de nuestra aplicacin sin afectar a la lgica de nuestra


aplicacin ni a la Base de datos

Bien como ya hemos mencionado La Arquitectura de diseo 3 capas, consiste en dividir el


diseo del software en sus tres principales componentes:

1. La Interfaz o UI (User interface): Esta Capa es la encargada de interactuar con el


usuario, es decir, son aquellas ventanas, mensajes, cuadros de dilogos o paginas web
(en el caso del desarrollo web), que el usuario final utiliza para comunicarse con la
aplicacin, por medio de esta capa el usuario solicita que se ejecuten las tareas
proporcionando parmetros de entrada y recibiendo datos como respuesta. Esta capa
se comunica con la capa de Lgica de Negocio, enviando y solicitando informacin y
con la capa de Entidades usando sus objetos para enviar y recibir esta informacin.

2. La lgica de negocio o Business Logic: Se encarga de implementar, como su


nombre lo dice, la lgica del negocio, es decir, todo lo que el Software debe de
considerar antes de realizar una accin o el proceso que debe de seguir despus de
realizar una accin. Por ejemplo: Antes de solicitar a la capa de Datos la insercin de
un grupo de registros en una tabla, valida que vayan todos los campos mandatorios
dentro de esa solicitud si esta condicin no se cumple entonces rechaza la insercin e
informa del usuario del status de su solicitud; otro ejemplo podria ser, solicitar a la base
de datos que valide la presencia de un registro antes de insertar el siguiente, validar los
tipos de datos, etc. esos ejemplos por mencionar los mas bsicos y generales. Esta
capa recibe de la Capa de Presentacin las solicitudes, valida que las condiciones que
establece el negocio se cumplan antes de realizar dicha accin o de hacer la respectiva
solicitud a la Capa de Acceso a Datos

3. El acceso a Datos o Data Access: Esta capa es la encargada de la comunicacin con


la base de datos, en esta capa descansaran todas nuestras acciones CRUD (Create,
Read, Update y Delete), ser la nica que sabr que motor de base de datos se esta
utilizando pero le ser completamente desconocido el front, es decir, jams sabr si
nuestra aplicacin es una aplicacin web o desktop. Se encarga de recibir las
peticiones de la Capa de Lgica de Negocio, ejecutar dichas acciones y devolver el
resultado a la misma capa.

4. Capa de Entidades o Entity Layer: Aunque aparentemente es una cuarta capa


realmente no lo es, esta capa se encarga de contener todos aquellos objetos (clases)
que representan al negocio, y esta es la nica que puede ser instanciada en las 3
capas anteriores, es decir, solo ella puede tener comunicacin con el resto pero su
funcin se limita a nicamente ser un puente de transporte de datos. Esta capa
complementa a la Capa de Negocio
Para una mejor compresin de la comunicacin de las 3 capas:

Hasta aqu, hemos visto la teora de lo que representa la Arquitectura 3 Capas, pero

Como es que debo representar la Arquitectura 3 Capas en un proyecto de Visual


Studio?

Para haya es a donde vamos.

1. Abra el Visual Studio 2012 y cree un proyecto Vaco y nmbrelo CSharp-3Capas-Primer


Entrega

2. Dirjase al Explorador de soluciones y haga click derecho sobre el proyecto que acabamos
de crear:
3. Del men desplegado seleccione Agregar->Nuevo Proyecto:

4. Seleccione, Instalado > Visual C# > Windows > Aplicacin de Windows Forms > En el
campo Nombre escriba, Tienda-Presentacion y presione el botn Aceptar.

5. Repita el paso 2, 3 y en el 4, seleccione un tipo de proyecto Biblioteca de Clases y


establezca como nombre Tienda-LogicaNegocio

6.Repita el paso 2, 3 y en el 4, seleccione un tipo de proyecto Biblioteca de Clasesutilice el


nombre Tienda-AccesoDatos

7. Repita el paso 2, 3 y en el 4, seleccione un tipo de proyecto Biblioteca de Clases utilice el


nombre Tienda-Entidades

Como va nuestro diseo'?

Observe que cuando creamos el proyecto principal en automtico se creo un proyecto vaco,
eliminemos ese proyecto para dejar la siguiente estructura:

Ahora ya tenemos nuestra estructura completa, observe que creamos 4 proyectos dentro del
principal (que lo iniciamos como proyecto vaco), recuerde que el proyecto de Entidades en
este caso Tienda-Entidades no es una capa, mas bien, complementa a la capa de Lgica de
Negocio.

Continuemos con nuestro diseo

Capa de Entidades

Recuerde que en esta capa estarn alojados los objetos (clases) con los cuales estaremos
trabajando, con estos objetos estaremos persistiendo las tablas de la base de datos que
utilizaremos.

La base de datos que usaremos contiene una sola tabla llamada Producto la cual contiene 5
campos (Id int, Descripcin nvarchar, Marca nvarchar, Precio nvarchar), por lo cual la
Capa de Entidades contendr un Objeto llamado Producto con 5 propiedades publicas cada
uno de ellos respetando el tipo de dato con el cual esta declarado en la base de datos.

Para declarar nuestra Entidad Producto:

1. Agregue una clase al proyecto Tienda-Entidades y llmela EProducto (La letra E es por
convecin, es decir, todos los objetos de nuestra capa de Entidades llevaran la letra E al
principio para que desde donde las lleguemos a utilizar sepamos que ese Objeto es una
Entidad evitando con esto confusiones con otros objetos), dentro agregue las siguientes lneas
de cdigo:

font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tienda_Entidades
{
public class EProducto
{
public int Id { get; set; }
public string Descripcion { get; set; }
public string Marca { get; set; }
public decimal Precio { get; set; }
}
}

Si tuviramos mas tablas en nuestra base de datos tendramos que crear la misma cantidad de
objetos en nuestra capa de Entidades y dentro contendran la misma cantidad de propiedades
como campos tiene la tabla siempre respetando el tipo de dato con lo que estos campos estn
declarados.
Capa de acceso a Datos
Recordemos que la capa de datos es usada por la capa de lgica y la capa de lgica es
llamada por la capa de presentacin, as que usaremos ese mismo orden para crear nuestras
configuraciones y nuestras lneas de cdigo.

1. Agreguemos un archivo de configuracin (App.Config) a nuestra capa de datos recuerde que


una de las caractersticas principales del archivo de configuraciones es que podemos
establecer una cadena de conexin la cual accederemos fcilmente desde cualquier lugar del
proyecto que lo contiene, que es lo que en esta ocasin nos interesa.

Nuestro archivo de configuraciones lo tenemos que agregar en nuestro proyecto de


presentacin, y no porque lo vaya a utilizar sino que nuestro software cada vez que arranque
buscara este archivo en el directorio desde donde se este ejecutando la aplicacin, para
efectos de prueba ser en la carpeta ..bin\debug pero cuando nuestro proyecto este instalado,
el archivo quedara desplegado justo en la carpeta raz de nuestra instalacin junto con el .exe
por tal motivo deber de estar en el mismo proyecto que esta marcado como proyecto de inicio.

Para agregar un archivo de configuraciones siga estos pasos:

- Click derecho sobre el proyecto Tienda-AccesoDatos > Agregar > Nuevo elemento

2. En el proyecto que descargaron anteriormente si observaron viene embebida un archivo de


base de datos llamado DataBase1.sdf que noes otra cosa mas que un archivo de base de
datos propios del motor SQlCompact, cree una carpeta en la unidad C y llmela Proyecto-
3Capas, despus copien este archivo en ese path.

3. Abran el archivo de configuraciones desde el Explorador de soluciones, Copien las


siguientes lneas de cdigo, borren el contenido del App.Config y pguenlas en su lugar:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<configSections>
</configSections>
<connectionStrings>
<add name="cnnString"
connectionString="Data Source=C:\Proyecto-
3Capas\Database1.sdf"
providerName="Microsoft.SqlServerCe.4.0" />
</connectionStrings>
<startup>
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>

Observen que el DataSource apunta a la carpeta que acabamos de crear en la unidad C Data
Source=C:\Proyecto-3Capas\Database1.sdf", esta cadena en proyetos reales normalmente
apuntara a un servidor de datos, pero para fines ilustrativos, este directorio nos servir muy
bien.

Volvamos a nuestra Capa de Datos

4. Agreguemos las referencias a las libreras System.Configuration para esto, Click sobre
nuestra capa de datos (proyecto Tienda-AccesoDatos) > Agregar Referencia

5. Agregue una segunda referencia ahora a System.Data.SqlServeCe (esta librera no siempre


aparece, si este es su caso presione el botn Examinar localice la Dll dentro de archivos de
programa y seleccinela), presione Aceptar.

6. Ya tenemos nuestro App.Config(en el proyecto de arranque) apuntando a nuestra base de


datos y tenemos las dos referencias que necesitamos, System.Configuration para poder
accesar y leer el archivo de configuraciones y el System.Data.SqlServerCe para poder usar los
objetos de acceso a datos propios del motor SQLCOMPACT en nuestra Capa de Datos.

Como nuestra capa de Acceso a Datos, debe de tener la capacidad de Insertar, Leer, Actualizar
y Eliminar registros de la tabla Productos, requiere de una comunicacin directa con la capa de
Entidades, para por ejemplo, al momento de que se le solicite la insercin de un Producto en
lugar de enviarle 5 parmetros con los datos del producto se le envi un Objeto y Cual ser
este objeto? nada mas y nada menos que nuestro objeto EProducto creado en la capa de
Entidades, de esta manera ya no trabajaremos con datos sino con objetos llenando, enviando y
leyendo propiedades.

Para lograr la comunicacin entre la Capa de Datos y la Capa de Entidades se requiere de la


referencia de una en la otra, en este caso la referencia de la Capa de Entidades dentro de la
Capa de Datos, para ello:

7. Click derecho sobre nuestro proyecto Tienda-AccesoDatos > Agregar Referencia

8. Solucin > Proyectos > Seleccione Tienda-Entidades

Observe como se a creado un nuevo elemento en nuestra carpeta de Referencias del proyecto
Tienda-AccesoDatos
9. Inserte una clase nueva llamada ProductoDal de donde Dal vendr de Data Access Layer,
dentro de la clase que acaba de crear tiene que declarar el espacio de nombres
System.Configuration, System.Data.SqlServerCe y del proyecto de Entidades, adems tiene
que declarar la clase como publica (para que la Capa de Lgica de Negocio pueda tener
acceso a ella)

Ya tenemos el puente de comunicacin entre nuestras Entidades y nuestra Capa de Datos.


Solo resta comenzar a codificar las acciones que querramos hacer con nuestra tabla Producto,
recuerde que el objeto ProductoDal nicamente tiene como responsabilidad trabajar con todo
aquello relacionado con Producto, as que comencemos haciendo la codificacin para Insertar,
Traer todos los registros existentes en nuestra tabla Producto, Traer, Actualizar y Eliminar por
Id.

Para hacer esta tarea mas sencilla le proporcionare el cdigo que deber de poner en la clase
ProductoDal, pero, trate de escribir el cdigo para vaya analizando la estructura del mismo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//nuestras importaciones del Espacio de nombres que estaremos
utilizando,
//recuerde que estas son las referencias que realizamos hace unos
momentos...
using System.Configuration;
using System.Data.SqlServerCe;
using Tienda_Entidades;
namespace Tienda_AccesoDatos
{
//Definimos el acceso de nuestra clase como public, asegurando con
esto su accesibilidad desde
//otros proyectos.
public class ProductoDal
{
//Primero y siguiendo el orden de las acciones CRUD
//Crearemos un Mtodo que se encarga de insertar un nuevo
Producto es nuestra tabla Producto
/// <summary>
/// Inserta un nuevo Producto en la tabla Producto
/// </summary>
/// <param name="producto">Entidad contenedora de los valores
a insertar</param>
/// <autor>Jos Luis Garca Bautista</autor>
public void Insert(EProducto producto)
{
//Creamos nuestro objeto de conexion usando nuestro
archivo de configuraciones
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].To
String()))
{
cnx.Open();
//Declaramos nuestra consulta de Accin Sql
parametrizada
const string sqlQuery =
"INSERT INTO Producto (Descripcion, Marca, Precio)
VALUES (@descripcion, @marca, @precio)";
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery,
cnx))
{
//El primero de los cambios significativos con
respecto al ejemplo descargado es que aqui...
//ya no leeremos controles sino usaremos las
propiedades del Objeto EProducto de nuestra capa
//de entidades...
cmd.Parameters.AddWithValue("@descripcion",
producto.Descripcion);
cmd.Parameters.AddWithValue("@marca",
producto.Marca);
cmd.Parameters.AddWithValue("@precio",
producto.Precio);

cmd.ExecuteNonQuery();
}
}
}

/// <summary>
/// Devuelve una lista de Productos ordenados por el campo Id
de manera Ascendente
/// </summary>
/// <returns>Lista de productos</returns>
/// <autor>Jos Luis Garca Bautista</autor>
public List<EProducto> GetAll()
{
//Declaramos una lista del objeto EProducto la cual ser
la encargada de
//regresar una coleccin de los elementos que se obtengan
de la BD
//
//La lista substituye a DataTable utilizado en el proyecto
de ejemplo
List<EProducto> productos = new List<EProducto>();

using (SqlCeConnection cnx = new


SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].To
String()))
{
cnx.Open();

const string sqlQuery = "SELECT * FROM Producto ORDER


BY Id ASC";
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery,
cnx))
{
SqlCeDataReader dataReader = cmd.ExecuteReader();
//
//Preguntamos si el DataReader fue devuelto con
datos
while (dataReader.Read())
{
//
//Instanciamos al objeto Eproducto para llenar
sus propiedades
EProducto producto = new EProducto
{
Id =
Convert.ToInt32(dataReader["Id"]),
Descripcion =
Convert.ToString(dataReader["Descripcion"]),
Marca =
Convert.ToString(dataReader["Marca"]),
Precio =
Convert.ToDecimal(dataReader["Precio"])
};
//
//Insertamos el objeto Producto dentro de la
lista Productos
productos.Add(producto);
}
}
}
return productos;
}

/// <summary>
/// Devuelve un Objeto Producto
/// </summary>
/// <param name="idProducto">Id del producto a buscar</param>
/// <returns>Un registro con los valores del
Producto</returns>
/// <autor>Jos Luis Garca Bautista</autor>
public EProducto GetByid(int idProducto)
{
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].To
String()))
{
cnx.Open();
const string sqlGetById = "SELECT * FROM Producto
WHERE Id = @id";
using (SqlCeCommand cmd = new SqlCeCommand(sqlGetById,
cnx))
{
//
//Utilizamos el valor del parmetro idProducto
para enviarlo al parmetro declarado en la consulta
//de seleccin SQL
cmd.Parameters.AddWithValue("@id", idProducto);
SqlCeDataReader dataReader = cmd.ExecuteReader();
if (dataReader.Read())
{
EProducto producto = new EProducto
{
Id = Convert.ToInt32(dataReader["Id"]),
Descripcion =
Convert.ToString(dataReader["Descripcion"]),
Marca =
Convert.ToString(dataReader["Marca"]),
Precio =
Convert.ToDecimal(dataReader["Precio"])
};

return producto;
}
}
}

return null;
}

/// <summary>
/// Actualiza el Producto correspondiente al Id proporcionado
/// </summary>
/// <param name="producto">Valores utilizados para hacer el
Update al registro</param>
/// <autor>Jos Luis Garca Bautista</autor>
public void Update(EProducto producto)
{
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].To
String()))
{
cnx.Open();
const string sqlQuery =
"UPDATE Producto SET Descripcion = @descripcion,
Marca = @marca, Precio = @precio WHERE Id = @id";
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery,
cnx))
{
cmd.Parameters.AddWithValue("@descripcion",
producto.Descripcion);
cmd.Parameters.AddWithValue("@marca",
producto.Marca);
cmd.Parameters.AddWithValue("@precio",
producto.Precio);
cmd.Parameters.AddWithValue("@id", producto.Id);

cmd.ExecuteNonQuery();
}
}
}

/// <summary>
/// Elimina un registro coincidente con el Id Proporcionado
/// </summary>
/// <param name="idproducto">Id del registro a
Eliminar</param>
/// <autor>Jos Luis Garca Bautista</autor>
public void Delete(int idproducto)
{
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].To
String()))
{
cnx.Open();
const string sqlQuery = "DELETE FROM Producto WHERE Id
= @id";
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery,
cnx))
{
cmd.Parameters.AddWithValue("@id", idproducto);

cmd.ExecuteNonQuery();
}
}
}
}
}

Observe como desde nuestros diferentes Mtodos y funciones estamos haciendo uso del
objeto EProducto declarado en la capa de EntidadesObserve tambin como nuestra clase
ProductosDal no valida que el valor de las propiedades de EProducto contengan datos o sean
del tipo de dato correcto porque ese es trabajo de?La Capa de Lgica de Negocio

Capa de Lgica de Negocio


Recuerde que la capa de Lgica es la encargada de establecer toda la lgica que el negocio
establece para llevar a cabo una accin o despus de haber realizado un proceso, y esta se
comunica directamente con la Capa de Acceso a Datos, por lo cual tenemos que hacer la
referencia al Proyecto Tienda-AccesoDatos y para ello:

1. Click derecho sobre nuestro proyecto Tienda-LogicaNegocio > Agregar Referencia


8. Solucin > Proyectos > Seleccione Tienda-AccesoDatos y Tienda-Entidades

Observe como se acaban de agregar nuestras referencias:

9. Agregue una nueva clase y llmela ProductoBol, haga los using de las referencias que
acabamos de crear, establezca el nivel de acceso como public y copie la siguiente estructura
de cdigo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
//Hacemos las importaciones del espacio de nombres de los dos
proyectos que referenciamos
//observe como esta capa solo referencio a Tienda-AccessData y no a
Tienda-Presentacion
//observe tambin como aqu no es requerida la referencia a
System.Data.SqlServerCe
using Tienda_AccesoDatos;
using Tienda_Entidades;
namespace Tienda_LogicaNegocio
{
public class ProductoBol
{
//Instanciamos nuestra clase ProductoDal para poder utilizar
sus miembros
private ProductoDal _productoDal = new ProductoDal();
//
//El uso de la clase StringBuilder nos ayudara a devolver los
mensajes de las validaciones
public readonly StringBuilder stringBuilder = new
StringBuilder();

//
//Creamos nuestro mtodo para Insertar un nuevo Producto,
observe como este mtodo tampoco valida los el contenido
//de las propiedades, sino que manda a llamar a una Funcin
que tiene como tarea nica hacer esta validacin
//
public void Registrar(EProducto producto)
{
if(ValidarProducto(producto))
{
if (_productoDal.GetByid(producto.Id) == null)
{
_productoDal.Insert(producto);
}
else
_productoDal.Update(producto);

}
}

public List<EProducto> Todos()


{
return _productoDal.GetAll();
}

public EProducto TraerPorId(int idProduct)


{
stringBuilder.Clear();

if (idProduct == 0) stringBuilder.Append("Por favor


proporcione un valor de Id valido");

if(stringBuilder.Length == 0)
{
return _productoDal.GetByid(idProduct);
}
return null;
}

public void Eliminar(int idProduct)


{
stringBuilder.Clear();

if (idProduct == 0) stringBuilder.Append("Por favor


proporcione un valor de Id valido");

if (stringBuilder.Length == 0)
{
_productoDal.Delete(idProduct);
}
}

private bool ValidarProducto(EProducto producto)


{
stringBuilder.Clear();

if (string.IsNullOrEmpty(producto.Descripcion))
stringBuilder.Append("El campo Descripcin es obligatorio");
if (string.IsNullOrEmpty(producto.Marca))
stringBuilder.Append(Environment.NewLine + "El campo Marca es
obligatorio");
if (producto.Precio <= 0)
stringBuilder.Append(Environment.NewLine + "El campo Precio es
obligatorio");

return stringBuilder.Length == 0;
}
}
}

Analice cada uno de los mtodos y funciones que creamos en nuestro primer objeto de nuestra
capa de Negocio, observe:

Cada uno de ellos tiene una sola responsabilidad

La capa de negocio cumple otras tareas y no solo la de ser un puente entre nuestra
Capa de Datos y nuestra Capa de Presentacin

Como es que estamos usando nicamente objetos y no controles, recuerde que esta
capa tampoco sabe que tipo de proyecto es el que la estar usando.

Observe que esta capa no tiene referencias a System.Configuration ni mucho menos a


System.Data.SqlServerCe.

Observe que en esta capa tambin se utiliza la Capa de Entidades.

Capa de Presentacin o User Interface


Le toca el turno a la Capa de Presentacin entrar en escena, esta capa ser la encargada de
interactuar con el usuario, la vista de todo nuestro sistema, la encargada de recoger las
peticiones del usuario y de pasar esta misma a la capa de Lgica de Negocio, todo lo que el
usuario requiera se la solicitara a esta y todo lo que Lgica de Negocio devuelva esta se la
mostrar al usuario en forma de datos.

Para configurar nuestra Capa de Presentacin.

10. Haga las referencias a los proyecto Tienda-LogicaNegocio y Tienda-Entidades


11. En el formulario que tenemos por default llamado Form1 disee una interfaz como la
siguiente:

12. Observe las siguientes lneas de cdigo, genere los eventos involucrados y copie el cdigo
de ejemplo:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//
//Hacemos las importaciones del espacio de nombres de los dos
proyectos que referenciamos
//observe como esta capa solo referencio a Tienda-LogicNegocio y a
Tienda-Entidades
//observe como no se referencia a la clase de acceso a Datos
using Tienda_LogicaNegocio;
using Tienda_Entidades;
namespace Tienda_Presentacion
{
public partial class Form1 : Form
{
//
//
//Creamos las instancias de la clase Eproducto y ProductoBol
private EProducto _producto;
private readonly ProductoBol _productoBol = new ProductoBol();

public Form1()
{
InitializeComponent();
}

//
//Creamos los mtodos generales llenando y leyendo objetos
//
private void Guardar()
{
try
{
if (_producto == null) _producto = new EProducto();

_producto.Id = Convert.ToInt32(txtId.Text);
_producto.Descripcion = txtDescripcion.Text;
_producto.Marca = txtMarca.Text;
_producto.Precio = Convert.ToDecimal(txtPrecio.Text);

_productoBol.Registrar(_producto);

if (_productoBol.stringBuilder.Length != 0)
{

MessageBox.Show(_productoBol.stringBuilder.ToString(), "Para
continuar:");
}
else
{
MessageBox.Show("Producto registrado/actualizado
con xito");

TraerTodos();
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error: {0}",
ex.Message), "Error inesperado");
}
}

private void TraerTodos()


{
List<EProducto> productos = _productoBol.Todos();

if (productos.Count > 0)
{
dgvDatos.AutoGenerateColumns = false;
dgvDatos.DataSource = productos;
dgvDatos.Columns["columnId"].DataPropertyName = "Id";
dgvDatos.Columns["columnDescripcion"].DataPropertyName
= "Descripcion";
dgvDatos.Columns["columnMarca"].DataPropertyName =
"Marca";
dgvDatos.Columns["columnPrecio"].DataPropertyName =
"Precio";
}
else
MessageBox.Show("No existen producto Registrado");
}

private void TraerPorId(int id)


{
try
{
_producto = _productoBol.TraerPorId(id);

if (_producto != null)
{
txtId.Text = Convert.ToString(_producto.Id);
txtDescripcion.Text = _producto.Descripcion;
txtMarca.Text = _producto.Marca;
txtPrecio.Text =
Convert.ToString(_producto.Precio);
}
else
MessageBox.Show("El Producto solicitado no
existe");
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error: {0}",
ex.Message), "Error inesperado");
}
}

private void Eliminar(int id)


{
try
{
_productoBol.Eliminar(id);

MessageBox.Show("Producto eliminado
satisfactoriamente");

TraerTodos();
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error: {0}",
ex.Message), "Error inesperado");
}
}
//
//
//Usamos nuestros metodos y funciones generales, observe como
no hemos repetido codigo en ningun lado
//haciendo con esto que nuestras tareas de actualizacion sean
mas sencillas para nosotros o para
//al asignado en realizarlas...
private void btnAgregar_Click(object sender, EventArgs e)
{
Guardar();
}

private void txtId_KeyDown(object sender, KeyEventArgs e)


{
if (e.KeyData == Keys.Enter && !
string.IsNullOrWhiteSpace(txtId.Text))
{
e.SuppressKeyPress = true;

TraerPorId(Convert.ToInt32(txtId.Text));
}
}

private void txtPrecio_KeyDown(object sender, KeyEventArgs e)


{
if (e.KeyData == Keys.Enter)
{
e.SuppressKeyPress = true;

Guardar();
}
}

private void btbnBuscar_Click(object sender, EventArgs e)


{
if (!string.IsNullOrWhiteSpace(txtId.Text))
{
TraerPorId(Convert.ToInt32(txtId.Text));
}
}

private void btnEliminar_Click(object sender, EventArgs e)


{
if (!string.IsNullOrWhiteSpace(txtId.Text))
{
Eliminar(Convert.ToInt32(txtId.Text));
}
}
}
}

De nuevo observe como esta capa desconoce si existe una capa de Datos y mucho menos que
motor de base de datos se utiliza, tampoco se encarga de implementar las reglas de validacin
ni de lgica de negocio, su tarea es interactuar con el usuario pidiendo y desplegando
informacin.

Que es lo que falta?

Solo nos resta probar el proyectopero eso, ser tarea suyaEjecute el proyecto, inserte un
nuevo registro, busque un registro por id, edite su informacin, elimine un producto, depure el
cdigo lnea a lnea para viva de paso a paso como es que va pasando de capa de capa
enviando y trayendo informacin.

Aqu termina nuestro articulo sobre Arquitectura 3 Capas, espero haya sido de su agrado y que
la explicacin haya sido lo bastante clara, en caso de que tenga alguna duda por favor deje su
pregunta en la seccin de comentarios o escrbame por medio del formulario de contacto.

Escribir este articulo me llevo mas de 4 horas, dejar un comentario de agradecimiento le tomara
5 minutos.

Saludos desde Monterrey, Nuevo Len Mxico!

Ejemplo C#
Ejemplo Vb.Net

Introduccin a Programacin en Capas con Visual Studio 2008


with 9 comments

12 Votes

Si bien todos hemos odo hablar sobre la Programacin en Capas, y de los beneficios
que trae como mantenibilidad, escalabilidad, y reusabilidad de cdigo, no todos
sabemos cul es la mejor forma de llevarlo a cabo al empezar un proyecto con
tecnologa Microsoft, y para este caso, con Visual Studio 2008.

En primer lugar, debemos definir cuntas capas va a tener nuestro proyecto. Por lo
general, todo proyecto consta de 3 capas: Presentacin, Reglas de Negocio y Acceso a
Datos. Sin embargo, es posible que se aada una capa de Servicios que exponga los
mtodos construidos en la capa de Reglas de Negocio para que puedan ser consumidos
por otros sistemas, y no slo por nuestra capa de Presentacin.

Para comenzar a construir nuestra solucin, debemos iniciar el VS2008. Hacemos click
en File -> Other Project Types -> Visual Studio Solutions -> Blank Solution ->
Introducimos el nombre de nuestra preferencia en Name -> OK.
A continuacin, agregamos 3 carpetas, es decir, una para cada capa, las cuales
contendrn los proyectos que las conformarn. Hacemos click derecho en la Solucin
creada previamente, que aparece en el panel de la derecha -> Add -> New Solution
Folder -> Introducimos el nombre de la carpeta. Repetir el procedimiento para cada
carpeta. Al finalizar, debemos tener lo siguiente:
Este paso no es estrictamente obligatorio, pero sirve para mantener un mayor orden en
el desarrollo de la aplicacin.

Posteriormente, pasamos a aadir los proyectos que vayamos a utilizar a cada una de las
carpetas ya creadas.

Para la capa de Presentacin, donde adems de ser la interfaz de usuario, tambin sirve
para recolectar y validar data, debemos definir qu tecnologa usar dependiendo de las
necesidades de nuestra aplicacin. VS2008 nos provee 4 opciones: Windows
Presentation Foundation, Windows Forms, Console Application y ASP.NET Web
Application. Para efectos de este ejemplo aadir un proyecto de WPF. Hacemos click
derecho en la carpeta Presentacion -> Add -> New Project -> Seleccionamos la
categora Visual C# (o Visual Basic si es de su preferencia) -> Windows -> WPF
Application -> Introducimos el nombre en Name -> OK.

Ahora bien, para la capa de Lgica de Negocios, donde estn todos los mtodos
relacionados directamente con la funcionalidad de nuestra aplicacin (Ej: clculo de
totales de facturas, realizar descuentos en compras al mayor, etc.), debemos agregar, en
primer lugar, un proyecto donde estarn todos los objetos de negocio, quienes se
comportarn como medio de transporte de datos entre las distintas capas. Segundo,
agregar el proyecto donde estn todas las clases con sus mtodos que nos ayuden a
implementar la funcionalidad de la aplicacin, tales como los ejemplos ya mencionados.
Estos 2 proyectos deben ser de tipo Class Library, y se agregan de forma similar al
proyecto de WPF. En esta capa tambin podramos usar algn proyecto de tipo
Windows Workflow Foundation, el cual ser discutido en futuros posts.

Para la capa de Acceso a Datos, vamos a definir cules sern los mtodos que nos
permitan interactuar con la BD, y poder implementar las operaciones CRUD. Para
lograr esto VS2008 con .NET Framework 3.5 provee distintos mecanismos, desde el uso
de objetos DataReader, DataAdapter, DataSets, pasando por LINQ, hasta llegar al
novedoso, pero cuestionado, Entity Framework, todos parte del equipo de ADO.NET.
Evidentemente, por su utilidad y complejidad, cada una de estas tecnologas merece un
post propio, por lo que quedarn para prximas discusiones. En el presente ejemplo,
slo agregaremos un proyecto de tipo Class Library, en el que se podrn implementar
las clases pertinentes.

Por ltimo, y para poder integrar los proyectos de nuestra solucin, es necesario asignar
ciertas referencias. Por ejemplo, todas las capas debern conocer los objetos de negocio
ya creados para poder trabajar con ellos. Para lograr esto es necesario realizar lo
siguiente: click derecho sobre nuestro proyecto de capa de Presentacin -> Add
Reference -> Projects -> Seleccionamos el proyecto de Objetos de Negocio -> OK.
De igual modo, recordemos que las clases que formarn nuestros objetos debern ser
pblicas (por default, son privadas), y debemos agregar la sentencia using
ObjetosNegocio; en cada una de las clases donde vayamos a hacer uso de los mismos.
Repetir este procedimiento en cada uno de los proyectos que hagan uso de los objetos
de negocio. Asimismo, debemos aadir las referencias del proyecto de mtodos de
negocio al proyecto de presentacin, as como el de acceso a datos al de mtodos de
negocio.

Para finalizar, la base de la solucin creada quedar as:


Cabe destacar que, si en un futuro se decidiera usar otra tecnologa diferente a WPF
para la capa de Presentacin, slo se deber aadir el nuevo proyecto a la solucin ya
creada, y al agregar las referencias pertinentes, podr hacer uso de todos los mtodos de
negocio y de acceso a datos ya desarrollados.

Espero este post pueda ser de utilidad para sus inicios en el desarrollo de futuros
proyectos.

Como siempre, espero su feedback.

Desarrollo de Aplicacin en Capas con Visual Studio 2008


with 19 comments

7 Votes
Continuando con el post de Introduccin a Programacin en Capas con Visual Studio
2008, el objetivo del presente artculo es dar un pequeo ejemplo tcnico y mucho ms
tangible del desarrollo en capas.

El objetivo de la aplicacin a desarrollar es ingresar personas a una base de datos,


haciendo uso de los objetos de ADO.NET, y posteriormente calcular su edad.

Para lograr esto, empezaremos por definir la base de datos, la cual slo tendr la tabla
persona con los campos: nombre, apellido y fechaNac.

Para efectos de este post no estoy considerando las mejores prcticas en la


implementacin de base de datos, dado que el objetivo es ilustrar la programacin en
capas.

A continuacin, desarrollamos una ventana en el proyecto UserInterfaceWPF


denominada PersonaUI:

Para el desarrollo de esta ventana se utiliz el componente DatePicker para la


seleccin de fecha de nacimiento. Este componente forma parte de la extensin WPF
Toolkit, que adems trae el DataGrid que no est incluido en el .NET Framework 3.5.
Cabe destacar que el proyecto de inicio por default debe ser el de la capa de
presentacin. Para lograr esto hacemos click derecho sobre UserInterfaceWPF y
seleccionamos Establecer como proyecto de inicio o Set as startup project.
Debemos recordar que para poder dar la funcionalidad requerida es necesario agregar
las referencias en cada proyecto. Para UserInterfaceWPF se debe agregar como
referencia a MetodosNegocio y ObjetosNegocio. Para MetodosNegocio se debe
agregar MetodosAccesoDatos y ObjetosNegocio. Y para MetodosAccesoDatos se
debe agregar ObjetosNegocio. Por ltimo se deben citar al inicio de las clases, por
ejemplo, en la clase de la ventana PersonaUI se debe aadir: using ObjetosNegocio; y
using MetodosNegocio;.

Desarrollando funcionalidad para la clase PersonaUI, donde estarn los manejadores


de los botones Guardar y Calcular edad:

El objeto PersonaON utilizado para definir al objeto persona, se refiere al objeto


fuertemente tipado del proyecto ObjetosNegocio, definido de la siguiente manera:
Por otro lado, los mtodos de la clase PersonaMN utilizados para insertarPersona y
calcularEdad fueron definidos de la siguiente manera:
De igual modo, los mtodos de la clase PersonaAD como insertarPersona o
buscarPorNombre fueron desarrollados como se muestra a continuacin:
Como han podido observar, en los mtodos de acceso a datos utilic una
connectionString completamente legible. sta no es la manera ms adecuada de
hacer uso de ella, sin embargo, este tema quedar para un futuro post. La forma ms
fcil de generar una cadena de conexin es haciendo uso del Server Explorer y del
wizard que provee para realizar este tipo de actividades haciendo click derecho en
Conexiones de datos y Agregar conexin. Como siempre, har falta el nombre del
servidor, instancia, base de datos, usuario y password.

Asimismo, tampoco se hizo manejo de excepciones, lo cual es parte importante de una


aplicacin, tanto por robustez como por seguridad. Este tema tambin ser tratado en un
prximo post.

La base de la solucin en el Solution Explorer debi quedar de la siguiente manera:

Por ltimo, pondremos en ejecucin la aplicacin desarrollada:


Al hacer click en Guardar se almacenar a la persona en la base de datos, tal y como
lo hemos definido. Y al introducir el nombre Alejandro y hacer click en Calcular
edad nos devolver su edad en das (tomando en cuenta que hoy es 14/08/2009):

Una vez ms, espero que este post pueda ser de ayuda para sus actividades como
desarrolladores. Bienvenidos sus comentarios, sugerencias, o lo que tengan a bien.

Entity Framework para Aplicaciones Empresariales

with 7 comments
6 Votes

Tal y como coment en el post de Introduccin a Programacin en capas con Visual


Studio 2008, el Entity Framework (EF) nos provee una nueva forma de generar nuestros
mtodos de acceso a datos CRUD (create, read, update y delete), adems de proveer
todo un diagrama de clases a partir del cual se generarn todos los objetos dependiendo
de las entidades y relaciones que formen nuestra base de datos. El EF es el ORM
(Object-relational mapping) de .NET Framework a partir de su versin 3.5, siendo su
principal competidor NHibernate, el cual nace de Hibernate, el ORM de Java por
naturaleza.

Sin duda, la conceptualizacin y objetivo del EF proporciona una serie de ventajas en el


desarrollo de SW:

Minimiza la cantidad de cdigo a desarrollar en aplicaciones


orientadas a data. Genera los objetos de negocio, y mtodos de
acceso a datos, es decir, gran parte de lo que generalmente
requerimos para el desarrollo de una aplicacin.

Independiente del motor de base de datos. A diferencia de LINQ,


el EF no trabaja exclusivamente con SQL Server, sino tambin con el
resto de sistemas manejadores de bases de datos como Oracle,
MySQL, PostgreSQL, etc.

En este blog podrn encontrar una serie de posts que les ayudar a empezar a desarrollar
aplicaciones n-layer con Entity Framework. En lo nico que discrepo con su autor es
que el modelo .edmx debera generarse en la capa de Reglas de Negocios, dado
que es aqu donde, por definicin, deben estar todos los objetos de negocio.

Cabe destacar, que el EF tiene su lenguaje propio que permite mayor interaccin y
flexibilidad en las operaciones de acceso a datos. Dicho lenguaje es el Entity SQL.

Sin embargo, cuando se trata de desarrollos para Aplicaciones Empresariales, donde la


base de datos contenga aproximadamente 100 tablas o ms, y que exista un promedio de
dos relaciones por tabla, el funcionamiento del Entity Framework est lejos de ser el
esperado. El propio equipo de ADO.NET, quienes desarrollaron el EF, ha reconocido
los problemas mediante este post en su blog. De manera resumida, enumerar algunos
inconvenientes:

Excesivo tiempo de carga de la metadata que forma el EF.

Saturacin del modelo en el diseador del EF, lo que conlleva a su


ilegibilidad.

Mal comportamiento del IntelliSense provisto por Visual Studio,


debido al gran nmero de clases y atributos generados.

A pesar de que, en un segundo post, el equipo de ADO.NET ha intentado brindar


soluciones a estas fallas, stas no han convencido a sus usuarios, pues lo que intenta
hacer es adaptar al desarrollador a la forma de usabilidad del EF, condicionndolo,
cuando es el EF quien debe cumplir las funciones para las cuales fue desarrollado. A
continuacin, un resumen de estas posibles soluciones:

Subdividir el modelo de clases en modelos ms pequeos, haciendo


reutilizacin de tipos para poder completar funcionalidad. Esto
implicara meterle a mano al cdigo generado automticamente,
con todas las posibles consecuencias que esto pueda acarrear.

Seleccionar las tablas que estn desconectadas del modelo, es decir,


sin relaciones o foreign keys. Evidentemente, esto representara un
porcentaje muy bajo en relacin a todo el modelo ER.

Adems de mis crticas a las soluciones propuestas, aqu podrn encontrar otros
argumentos que sustentan la crtica constructiva al uso del Entity Framework en
aplicaciones con grandes modelos de base de datos.
Por ltimo, la buena noticia es que Microsoft, a travs de su equipo de ADO.NET, ha
seguido y seguir invirtiendo recursos para la evolucin y mejora del Entity Framework,
los cuales se podrn empezar a percibir a partir del .NET Framework 4.0 y Visual
Studio 2010.

Como siempre, bienvenidos sean sus comentarios.

Seguridad en Aplicaciones n-layer


with 2 comments

2 Votes

En primer lugar, debemos definir seguridad como una serie de tareas o medidas que
se deben implementar en todos los procesos de desarrollo de una aplicacin n-layer o
conformada por multicapas lgicas, con el fin de evitar cualquier tipo de ataque que
pueda acarrear consecuencias tales como: prdida o transformacin de datos, mostrar
informacin confidencial a usuarios no autorizados, entre otras.

Desde siempre, los principios de la seguridad en el desarrollo de aplicaciones han sido


los siguientes:

1. Autenticacin: su fin es determinar que cada usuario es quien dice ser. Formas de
implementarla:

Uso de login y password, considerando polticas como nmero de


caracteres mnimos que puedan conformar dicho password, o tipo de
caracteres por el cual estar formado (caracteres especiales,
alfanumricos, letras maysculas), y de esta manera reducir la
probabilidad de xito de los ataques por fuerza bruta.

Almacenar password y otros datos confidenciales de manera


encriptada en la base de datos. Esto se puede lograr mediante
cualquiera de los algoritmos de encriptamiento que trae el .NET
Framework desde su versin 2.0. MD5, SHA1, son ejemplo de ellos.
Luego de recibir el parmetro mediante la Capa de Presentacin, se
debe encriptar para su futura insercin o comparacin con lo
almacenado en la BD. El fin de esta medida es evitar la fuga de
informacin confidencial relativa a la aplicacin en los casos que se
intente acceder a ella directamente desde la BD.
Lmite mximo de intentos por usuario. Generalmente se implementa
en aplicaciones que requieran un alto nivel de seguridad, y consiste
en bloquear (para lo cual deber tener un atributo relativo a estatus
en la tabla Usuario de BD donde se almacenar esta informacin) a
todo usuario que haya excedido el nmero mximo de intentos
fallidos de autenticacin, es decir, dupla de login y password
incorrecta. Esta forma de proteccin tambin se debe a evitar el xito
de ataques por fuerza bruta.

2. Autorizacin: Se refiere al uso de roles y perfiles, tanto a nivel de la aplicacin,


como de BD. Su fin es determinar que cada usuario vea y haga justamente lo que le
corresponde ver y hacer, sin tener que estar necesariamente limitando su campo de
accin dentro de lo que corresponde a sus funciones en la aplicacin.

Genralmente, el rol de un usuario es establecido a priori y almacenado en la BD, para


que, a partir de ste, se pueda saber, desde la aplicacin, que componentes pueden ser
mostrados en su sesin. Dependiendo de la finalidad o funcionalidad del proyecto que
ests desarrollando podrs escoger entre no mostrar o bloquear los componentes. Si
deseas que un rol en especfico no sepa de la existencia de ciertas funcionalidades, lo
ideal es que no muestres dichos componentes, y en caso contrario, lo mejor es
bloquearlo, para que el usuario sepa que dicha funcionalidad existe, y que para acceder
a ella slo deber modificar su perfil, siguiendo determinados procesos definidos por su
organizacin.

3. Auditora: Consiste en dejar una traza o huellas de quin hizo qu, cundo y, si es
posible, para qu. Esta informacin debe ser confidencial y slo debe estar accesible
para los administradores del sistema con mayor privilegio. Lo ideal es que no pueda ser
modificada bajo ningn concepto, y es lo que finalmente otorgar credibilidad o
confianza al uso de la aplicacin. Como norma, se tiene que esta informacin va
almacenada de manera encriptada en la BD, aunque tambin es posible implementar
otras formas de almacenado en distintos lugares fsicos y con otras tecnologas como
logs de transacciones en archivos planos.

Una vez definidos estos tres aspectos, tambin debemos tener en cuenta cules son las
mejores prcticas a seguir mientras codificamos.

Prevenir el SQL Injection. Desde la Capa de Presentacin, encargada de validar y


recolectar datos, debemos crear funciones que nos permitan identificar si ciertos
parmetros podran estar siendo usados con fines de SQL Injection. En resumen, estas
rutinas debern validar que no existan caracteres como: (;) ( ) ( ) entre otros. Cabe
destacar, que el .NET Framework tambin tiene su librera propia para la validacin de
datos, System.Text.RegularExpressions, la cual tambin podr ser de utilidad para
lograr prevenir estos ataques. Mientras que el Enterprise Library tambin provee el
Validation Application Block que persigue el mismo fin.

Nunca escribir parmetros de configuracin o informacin sensible en el cdigo de la


aplicacin, hacerlo de manera encriptada. Para el caso de los Connection String, una
prctica recomendada es hacer uso del Enterprise Library, especficamente del Data
Access Application Block, el cual es capaz de encriptar dicha cadena de conexin y
almacenarla en el app.config o web.config, segn sea el caso.

Implementar polticas de manejo de excepciones. Esto trae dos consecuencias positivas.


La primera es la robustez de la aplicacin ante cualquier eventualidad esperada o no. La
segunda, evitar mostrar informacin que no deba conocer el usuario final, o posible
atacante de nuestro sistema, que le permita tener una idea ms clara de cmo est
estructurada nuestra aplicacin, mediante los mensajes de error transmitidos por
defecto. Lo ideal es llevar un log de excepciones, y mostrar mensajes por defecto para
cada una de las posibles excepciones determinadas previamente. Para llevar a cabo esta
tarea, de nuevo, es de utilidad el uso de Enterprise Library en sus Logging y Exception
Handling Application Blocks.

Con esto termino el que, espero, sea el primero de muchos posts.

De antemano, agradezco cualquier aporte que tengan a bien, con el fin de reforzar este
contenido.

Potrebbero piacerti anche