En numerosas ocasiones me escriben usuarios del Web consultándome como comenzar a construir aplicaciones Web con Java. Hoy vamos a ver un posible modo.... y, como siempre, vamos a tratar cambiar la perspectiva típica....
Os advierto que en este caso no va a ser un tutorial muy básico....
No debemos confundir el medio y el fin. Nuestro objetivo es construir una aplicación y struts es unos más de los componentes técnicos que vamos a utilizar.
Lo más importante es definir de un modo inequívoco que es lo que queremos hacer, es decir, tomar los requisitos:
|
Queremos construir un subsistema para el Web www.adictosaltrabajo.com que nos permita:
|
Los objetivos vemos que nos son excesivamente ambiciosos pero nos puede valer. Los requisitos son independientes de la tecnología....
Ahora vamos a, antes de tirar una sola línea de código, profundizar en los problemas para anticipar posibles riesgos ligados a la indeterminación de los requisitos o la falta de análisis de potenciales riesgos.
Debemos hacernos algunas preguntas
Nos respondemos
|
También nos debemos preguntar ¿estas son todas las preguntas que nos tenemos que hacer? ¿estamos planteando bien la toma de requisitos?
Ya os adelanto que no lo estamos haciendo demasiado bien aunque esto es otra guerra que ya lucharemos algún día (conceptualización y acotamiento de un problema, valoración rápida de esfuerzo y coste, identificación de riesgos típicos y particulares, etc. )
|
Si no somos capaces de hacer una buena definición del problema utilizando buenas técnicas de análisis, los problemas en la fase de desarrollo serán mucho más grandes,
Además se nos juntarán muchos problemas más:
|
Primer problema
Realizamos una descomposición del trabajo a realizar (WBS Work Breakdown Structure)
E incluso nos planteamos una posible solución (aplicando nuestro conocimiento sobre patrones de asignación de responsabilidad [GRASP])

Si diseñamos, podemos cuestionar el diseño y hacernos preguntas del estilo.
¿Qué es más conveniente a la hora de validar los parámetros?
Pues no es tan sencillo y dependerá de distintas cosas:
Con estas preguntas podríamos sacar unas conclusiones que se podrían transportar al resto de componentes de mi aplicación (podemos prototipar para determinar si las conclusiones son correctas antes de haber tirado demasiado código en paralelo).
Nosotros (sin entrar en más detalles) vamos a tomar las siguientes decisiones (que pueden ser más o menos acertadas):
La comprobación del país la realizamos en GestorVisitas (configurando una consulta sobre la clase Validador)
La eliminación de palabras conflictivas la hacemos en la capa de presentación
Otras consideraciones de diseño
La obtención de una conexión a la base de datos (en sistemas concurrentes) es un potencial riesgo en las aplicaciones Web (cuello de botella) y vamos a tratar de anticipar el problema. Suerte que ya hicimos otro tutorial donde analizamos como configurar un pool de conexiones a la base de datos con Struts.
Aún así tenemos que hacernos más preguntas:
Bueno, se ponga como se ponga, parece que hace falta que nuestras funciones de negocio no sean las encargadas de gestionar las conexiones a la base de datos. Si ellas no las gestionan, significa que les debe llegar algo que les permita acceder a la conexión a la base de datos cuando sea necesario (esto es una posible aplicación del patrón de diseño de inversión de responsabilidad). Además este algo debe ser independiente del tipo de aplicación que estemos construyendo (del tipo de interfaz de usuario).

De momento hemos pintado unos cuantos elementos que es posible que no entendamos:
(No estaría mal repasar un poquito los patrones de diseño generales y los J2EE)
Conclusiones del diseño
Todavía no hemos tirado una línea y ya tenemos claras bastantes cosas.
¿SORPRENDIDOS? Es que para esto vale DISEÑAR
Pensar así (aunque el diseño no sea muy avanzado) requiere formación y entrenamiento. Nno os pongáis nerviosos que todo llega (yo todos los días aprendo algo nuevo).
Jamás debemos anticiparnos a la hora de empezar a escribir código.
Cuanto mejor esté definido lo que tenemos que hacer, menos nos costará hacerlo y menos frustrados nos encontraremos por retocar constantemente nuestro desarrollo.
Instalación del entorno
Lo primero que hacemos es buscar una herramienta cómoda para construir. A mi me gusta NetBeans y aunque ya hay una primera versión 4, vamos a dejarla un poco que se estabilice antes de usarla (estar siempre a la última es asumir demasiado riesgo)
Construimos un nuevo proyecto (da igual la versión 3.5 o 3.6)

Lo creamos

Le asignamos nombre

Nos descargamos la última versión de Struts (en este caso si utilizo la última versión porque me interesa comprobar su estado)

Descomprimimos el fichero y localizamos la aplicación Web ejemplo en blanco

Situamos el fichero en nuestra instalación del TOMCAT

Arrancamos TOMCAT y automáticamente se descomprime el directorio y está ya lista la aplicación Web para ser utilizada.
Montamos en NetBeans el directorio descomprimido

Y ya tenemos todo lo necesario para empezar a construir nuestra aplicación

Arrancamos la aplicación para verificar que no hay ningún problema de compatibilidad

Decisión del punto donde empezar a construir
Ahora tenemos que empezar por algún sitio (esto es normalmente lo más difícil de decidir).
Si utilizamos siempre el mismo FrameWork (sea el que sea), esta pregunta solo nos la tenemos que hacer solo una vez y aplicarla en la fase de diseño. Si estamos constantemente cambiando el modo de hacer las cosas, será difícil anticipar conclusiones. Si en una organización grande se deja esta decisión a criterio de cada equipo, sacar conclusiones globales es difícil y más aún extrapolar soluciones de problemas encontrados en producción.
Os recomiendo que empecéis siempre por la base de datos.... esto es realmente lo importante. Si en el futuro se pusiera de moda otro lenguaje y tenemos bien modelada la base de datos, la sustitución será menos traumática.
Diseño de la base de datos
Creamos una nueva base de datos con el panel de control de MySQL. Podéis ver como instalar MySQL y su
consola en este otro tutorial
.

Le asignamos un nombre a la base de datos

Creamos las tablas

Definimos los campos de la primera tabla

Le asignamos el nombre a la tabla

Y comprobamos el resultado

Y repetimos la operación con la tabla visitas (ojo que el país ahora no debe ser clave única)

Insertamos los primeros valores de prueba con lo que conseguimos descubrir las sentencias SQL que necesitaríamos en nuestros programas. Podéis ver en otro de nuestro tutoriales como generar el código Java para acceder a estas tablas sin saber demasiado.

Introducimos también algún valor en la tabla de países

Lo primero que hacemos es construir el formulario de entrada de datos.
En un formulario pueden ocurrir distintos problemas:
Utilizaremos un JSP con las etiquetas particulares que proporciona Struts para resolver los problemas anteriormente mencionados (aunque con el primero hay que hacer algo más).
Tenemos que entender como funciona un poquito el FrameWork Struts
Ya se que no es fácil la primera vez (y menos aún identificar los errores según aparecen). Vamos a construir únicamente el código que es particular del interfaz que hemos elegido y marcaremos en el código los puntos en los que hace falta acceder a las reglas de negocio y los datos (que eso ya es Java puro y duro)
Aunque la aplicación no es plenamente funcional con esta estructura de ficheros seréis capaz de reproducir el ejemplo. En rojo hemos marcado los únicos ficheros que hemos cambiado o creado. Debéis tener precaución con los ficheros en el directorio lib (ver el tutorial sobre como configura el pool de conexiones a la base de datos con struts). No os preocupéis de momento en la posición de los ficheros creados (eso es un problema menor a estas alturas aunque en el futuro si nos deberá preocupar)
* index.jsp * resultadocorrecto.jsp * visitas.jsp * ****META-INF * context.xml * MANIFEST.MF * ****pages * Welcome.jsp * ****WEB-INF * struts-bean.tld * struts-config.xml * struts-html.tld * struts-logic.tld * struts-nested.tld * struts-tiles.tld * tiles-defs.xml * validation.xml * validator-rules.xml * web.xml * ****classes * * MessageResources.properties * * * ****resources * * MessageResources.properties * * * ****webstruts * InsertaVisitasAction.class * InsertaVisitasAction.java * ****lib * commons-beanutils.jar * commons-collections.jar * commons-dbcp-1.1.jar * commons-digester.jar * commons-fileupload.jar * commons-lang.jar * commons-logging.jar * commons-pool-1.1.jar * commons-validator.jar * jakarta-oro.jar * jdbc2_0-stdext.jar * jstl.jar * mysql-connector-java-3.0.8-stable-bin.jar * standard.jar * struts-legacy.jar * struts.jar * ****src * build.xml * ****java ****resources application.properties |
A mi me gusta tocar los ficheros de configuración a mano, sobre todo al principio, pero no olvidéis que hay herramientas gráficas (consolas para struts) para poder hacerlo (incluso se integran fácilmente con nuestro editor)

El fichero de configuración * struts-config.xml
|
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> <!-- ============================================ Data Source Configuration --> <data-sources> <data-source type="org.apache.commons.dbcp.BasicDataSource"> <set-property property="driverClassName" value="com.mysql.jdbc.Driver" /> <set-property property="url" value="jdbc:mysql://localhost/visitasadictos" /> <set-property property="username" value="root" /> <set-property property="password" value="" /> <set-property property="maxActive" value="10" /> <set-property property="maxWait" value="5000" /> <set-property property="defaultAutoCommit" value="false" /> <set-property property="defaultReadOnly" value="false" /> <set-property property="validationQuery" value="SELECT COUNT(*) FROM paises" /> </data-source> </data-sources> <!-- ================================================ Form Bean Definitions --> <form-beans> <form-bean name="VisitasForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="pais" type="java.lang.Integer"/> <form-property name="observaciones" type="java.lang.String"/> </form-bean> </form-beans> <!-- ========================================= Global Exception Definitions --> <global-exceptions> <!-- sample exception handler <exception key="expired.password" type="app.ExpiredPasswordException" path="/changePassword.jsp"/> end sample --> </global-exceptions> <!-- =========================================== Global Forward Definitions --> <global-forwards> <!-- Default forward to "Welcome" action --> <!-- Demonstrates using index.jsp to forward --> <forward name="welcome" path="/Welcome.do"/> <forward name="correcto" path="/resultadocorrecto.jsp"/> </global-forwards> <!-- =========================================== Action Mapping Definitions --> <action-mappings> <!-- Default "Welcome" action --> <!-- Forwards to Welcome.jsp --> <action path="/Welcome" forward="/pages/Welcome.jsp"/> <action path="/InsertaVisistas" type="webstruts.InsertaVisitasAction" name="VisitasForm" scope="request" validate="true" input="/visitas.jsp"/> </action-mappings> <!-- ============================================= Controller Configuration --> <controller processorClass="org.apache.struts.tiles.TilesRequestProcessor"/> <!-- ======================================== Message Resources Definitions --> <message-resources parameter="MessageResources" /> <!-- =============================================== Plug Ins Configuration --> <plug-in className="org.apache.struts.tiles.TilesPlugin" > <!-- Path to XML definition file --> <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" /> <!-- Set Module-awareness to true --> <set-property property="moduleAware" value="true" /> </plug-in> <plug-in className="webstruts.InsertaVisitasAction" > </plug-in> <!-- =================================================== Validator plugin --> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in> </struts-config> |
La acción * InsertaVisitasAction.java
|
/* * InsertaVisitasAction.java * * Created on October 31, 2004, 12:27 PM * * Roberto Canales Mora */ package webstruts; import org.apache.struts.action.*; import javax.servlet.http.*; import java.util.*; public class InsertaVisitasAction extends Action implements org.apache.struts.action.PlugIn { public ActionForward execute(ActionMapping actionMapping,ActionForm actionForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ActionForward retValue = null; try // ejecutamos la funcion de negocio { // Obtenemos acceso al formulario DynaActionForm formulario = (DynaActionForm) actionForm; // aqui va la inserción de los datos System.out.println("El pais es: " + formulario.get("pais")); System.out.println("Observaciones: " + formulario.get("observaciones")); // redirigimos a la presentación retValue = actionMapping.findForward("correcto"); } catch(Exception e) // en caso de problemas retornar a la página origen { // recuperamos acceso al JSP deorigen retValue = actionMapping.getInputForward(); // damos de alta los errores ActionErrors errores = new ActionErrors(); errores.add(errores.GLOBAL_ERROR, new ActionError("error.logica")); errores.add("pais", new ActionError("error.enpais")); this.addErrors(httpServletRequest, errores); } return retValue; // redirigimos a la presentacion } /* * Los métodos destroy e init son obligatorios al implementar el interfaz PlugIn */ public void destroy() { } public void init(org.apache.struts.action.ActionServlet actionServlet, org.apache.struts.config.ModuleConfig moduleConfig) throws javax.servlet.ServletException { Hashtable vPaises = new Hashtable(); // aqui hay que poner el código que nos proporcione los datos de la base de datos vPaises.put("1","España"); // este codigo hay que quitarlo vPaises.put("2","Chile"); // mostramos un mensaje para asegurarnos que los datos están en memoria System.out.println("Tenemos el Vector"); // ponemos la tabla en el ambito de la aplicación actionServlet.getServletContext().setAttribute("tablapaises",vPaises); } } |
Esta acción tiene una peculiaridad y es que implementa en interfaz plugIn (métodos destroy e init) que nos permite que se ejecute código nada mas inicializarse el componente. La tabla de países no creo que cambie demasiado a menudo por lo que nos puede interesar recuperarla solo una vez para todos los usuarios del sistema.
El JSP de entrada de datos * visitas.jsp
|
<%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-html" prefix="html" %> <html> <head> <title><bean:message key="index.titulo"/></title> </head> <body> <center> <h2><bean:message key="index.cuentanostuorigen"/></h2> <hr width='60%'> <html:form action='/InsertaVisistas'> <table border="0"> <tr> <td>Pais</td><td> <html:select property="pais" > <html:options collection="tablapaises" property="key" labelProperty="value" /> </html:select> </td><td><html:errors property="pais" /> </td> </tr> <tr> <td><!-- podría ser una clave en el fichero de recursos --> Observaciones</td><td> <html:textarea property="observaciones" cols="50" rows="3" /> </td><td><html:errors property="observaciones" /></td> </tr> <tr><td> <html:submit value="Enviar"/></td> </tr> </table> </html:form> <br/> <font color="#FF0000"> <html:errors/> </font> </center> </body> </html> |

Analizamos este código con detenimiento porque tiene muchas más cosas de las que puede parecer en un principio.
|
¿De donde salen los valores del formulario? De los datos que ha puesto el plugIn en la aplicación <html:select property="pais" > |
|
¿De donde salen los textos de la página que no están directamente escritos? Del fichero de recursos <bean:message key="index.titulo"/>
|
|
¿Para que vale esta línea? Para mostrar errores cuando se produzcan problemas en validaciones <html:errors property="pais" /> Debemos buscar en la acción este código para entenderlo correctamente errores.add("pais", new ActionError("error.enpais")); |
JSP resultante * resultadocorrecto.jsp
|
<%@page contentType="text/html"%> <html> <head><title>JSP Page</title></head> <body> <h2>La acción se ejecutó satisfactoriamente</h2> </body> </html> |
Añadir el código de acceso a datos
Ahora que ya tenemos el código particular de Struts y nos queda el código de acceso a la base de datos. Esto es Java puro y duro.
Creamos el resto de las clases (aunque el código no es definitivo ya que el tratamiento de errores de momento es pésimo)
Visita.java
|
package webstruts; /* * Visita.java * Created on October 31, 2004, 6:17 PM * @author Roberto Canales */ public class Visita { private int id; private int pais; private String observaciones; private String ip; /** Creates a new instance of Visita */ public Visita() {} /** Creates a new instance of Visita */ public Visita(int pais, String observaciones,String ip) { this.pais = pais; this.observaciones = observaciones; this.ip = ip; } /** Getter for property id. * @return Value of property id. */ public int getId() {return id;} /** Setter for property id. * @param id New value of property id. */ public void setId(int id) {this.id = id;} /** Getter for property observaciones. * @return Value of property observaciones. */ public java.lang.String getObservaciones() {return observaciones;} /** Setter for property observaciones. * @param observaciones New value of property observaciones. */ public void setObservaciones(java.lang.String observaciones) { this.observaciones = observaciones;} /** Getter for property pais. * @return Value of property pais. */ public int getPais() {return pais;} /** Setter for property pais. * @param pais New value of property pais. */ public void setPais(int pais) {this.pais = pais;} /** Getter for property ip. * @return Value of property ip. */ public java.lang.String getIp() {return ip;} /** Setter for property ip. * @param ip New value of property ip. */ public void setIp(java.lang.String ip) {this.ip = ip;} } |
GestorVisitas.java
|
/* * GestorVisitas.java * * Created on October 31, 2004, 6:21 PM */ package webstruts; import javax.sql.*; import java.sql.*; /** * * @author Roberto Canales Mora */ public class GestorVisitas extends ClaseNegocio { /** Creates a new instance of GestorVisitas */ public GestorVisitas() { } void depura(String mensaje) { System.out.println("GestorVisitas - " + mensaje); } public boolean insertaVisita(Visita pVisita) { try { Connection con = this.dameConexion(); PreparedStatement pstmt = con.prepareStatement("insert into visitas (pais,observaciones,ip) values (?,?,?)"); // establecemos los valores variables pstmt.setInt(1,pVisita.getPais()); // establecemos el entero pstmt.setString(2,pVisita.getObservaciones()); // establecemos el entero pstmt.setString(3,pVisita.getIp()); // establecemos el entero // ejecutamos la consulta int resultado = pstmt.executeUpdate(); depura("El número de elementos afectados es " + resultado); // retornamos la conexion al pool con.close(); return true; } catch (SQLException e) { depura("Error al insertar " + e.getMessage()); return false; } } } |
ClaseNegocio.java
|
/* * ClaseNegocio.java * Created on October 31, 2004, 6:20 PM */ package webstruts; import javax.sql.*; import java.sql.*; /** * * @author Roberto Canales Mora */ public class ClaseNegocio { private javax.sql.DataSource fuenteDatos = null; /** Creates a new instance of ClaseNegocio */ public ClaseNegocio() { } public boolean inicializaFuenteDatos(javax.sql.DataSource pFuenteDatos) { fuenteDatos = pFuenteDatos; return true; } public java.sql.Connection dameConexion() { try { return fuenteDatos.getConnection(); } catch(Exception e) { } return null; } } |
InsertaVisistasAction.java (todavía hay que recuperar los países de la base de datos pero no queremos mezcla demasiadas cosas)
|
/* * InsertaVisitasAction.java * * Created on October 31, 2004, 12:27 PM * * Roberto Canales Mora */ package webstruts; import org.apache.struts.action.*; import javax.servlet.http.*; import java.util.*; public class InsertaVisitasAction extends Action implements org.apache.struts.action.PlugIn { public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm,HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ActionForward retValue = null; try // ejecutamos la funcion de negocio { // Obtenemos acceso al formulario DynaActionForm formulario = (DynaActionForm) actionForm; // Construimos el objeto de negocio GestorVisitas gestorVisitas = new GestorVisitas(); // le damos acceso a la fuente de datos gestorVisitas.inicializaFuenteDatos(this.getDataSource(httpServletRequest)); // recuperamos el pais y observaciones Integer pais = (Integer)formulario.get("pais"); String observaciones = formulario.get("observaciones").toString(); // llamamos al metodo de negocio boolean resultado = gestorVisitas.insertaVisita(new Visita(pais.intValue(), observaciones, httpServletRequest.getRemoteAddr())); // redirigimos a la presentación if(resultado == true) retValue = actionMapping.findForward("correcto"); else retValue = actionMapping.findForward("incorrecto"); } catch(Exception e) // en caso de problemas retornar a la página origen { // recuperamos acceso al JSP deorigen retValue = actionMapping.getInputForward(); // damos de alta los errores ActionErrors errores = new ActionErrors(); errores.add(errores.GLOBAL_ERROR, new ActionError("error.logica")); errores.add("pais", new ActionError("error.enpais")); this.addErrors(httpServletRequest, errores); } return retValue; // redirigimos a la presentacion } /* * Los métodos destroy e init son obligatorios al implementar el interfaz PlugIn */ public void destroy() { } public void init(org.apache.struts.action.ActionServlet actionServlet, org.apache.struts.config.ModuleConfig moduleConfig) throws javax.servlet.ServletException { Hashtable vPaises = new Hashtable(); // aqui hay que poner el código que nos proporcione los datos de la base de datos vPaises.put("1","España"); // este codigo hay que quitarlo vPaises.put("2","Chile"); // mostramos un mensaje para asegurarnos que los datos están en memoria System.out.println("Tenemos el Vector"); // ponemos la tabla en el ambito de la aplicación actionServlet.getServletContext().setAttribute("tablapaises",vPaises); } } |
Para que el ejemplo sea completamente utilizable necesitamos las tablas.
Vamos a recuperar la estructura de la tabla valiéndonos de phpMyAdmin (recordamos otro de nuestro tutoriales)

Elegimos las tablas y el modo de extracción.

Y nos genera algo tal que esto
|
# # Table structure for table `paises` # DROP TABLE IF EXISTS `paises`; CREATE TABLE `paises` ( `id` int(4) NOT NULL auto_increment,`pais` varchar(50) NOT NULL default '', `x` int(4) default '0',`y` int(4) default '0', PRIMARY KEY (`id`),UNIQUE KEY `paisesindex` (`pais`) ) TYPE=MyISAM AUTO_INCREMENT=5 ; # # Dumping data for table `paises` # INSERT INTO `paises` VALUES (1, 'España', 0, 0); INSERT INTO `paises` VALUES (2, 'Chile', 0, 0); INSERT INTO `paises` VALUES (3, 'Guatemala', 0, 0); INSERT INTO `paises` VALUES (4, 'Francia', 10, 10); # -------------------------------------------------------- # # Table structure for table `visitas` # DROP TABLE IF EXISTS `visitas`; CREATE TABLE `visitas` ( `id` int(11) NOT NULL auto_increment, `pais` int(4) unsigned zerofill NOT NULL default '0000', `observaciones` varchar(100) NOT NULL default '12', `ip` varchar(255) NOT NULL default '', PRIMARY KEY (`id`), KEY `indexpais` (`pais`) ) TYPE=MyISAM AUTO_INCREMENT=5 ; # # Dumping data for table `visitas` # INSERT INTO `visitas` VALUES (1, 0002, 'comentario1', '127.0.0.1'); INSERT INTO `visitas` VALUES (2, 0001, 'comentario2', '127.0.0.1'); INSERT INTO `visitas` VALUES (3, 0001, 'comentario3', '127.0.0.1'); INSERT INTO `visitas` VALUES (4, 0002, 'comentario4', '127.0.0.1'); |
Con esto ya tenemos todo lo necesario para orientar el programa.
Si tienes las ideas medianamente claras y eres capaz de descomponer un problema grande en problemas pequeñitos, no es tan difícil construir una aplicación.
Parece que hemos invertido demasiado esfuerzo para algo aparentemente trivial y que podríamos haber resuelto prácticamente con un par de JSPs o Servlets pero no nos engañemos: Todas las aplicaciones tiende a complicarse y si ha hemos constuido así será fácil ampliarla por cualquier sitio.
Aunque parezca contradictorio con el párrafo anterior, francamente creo que tendemos a tecnificar demasiado los problemas y nos dejamos llevar por las modas; No en todos los casos es necesario solucionar los problemas de un modo tan flexible (esto implica un coste inicial muy alto que puede hacer inviable un plan de negocio).
Struts es un buen posible punto de partida pero tampoco nos tenemos que equivocar:
Roberto Canales Mora
Ideal para el que sabe la teoríatugui | 2008-02-01 00:52:19
Justo lo que estaba buscando, sé como funciona Struts, pero no sabía como ponerme manos a la obra. En cuanto llegue a casa me pongo con ello. Muchas gracias.
Trabajos relacionados
Ver mas trabajos de Internet |
|
Nota al lector: es posible que esta página no contenga todos los componentes del trabajo original (pies de página, avanzadas formulas matemáticas, esquemas o tablas complejas, etc.). Recuerde que para ver el trabajo en su versión original completa, puede descargarlo en formato DOC desde el menú superior.