Cada vez menos ordenadores de sobremesa visitan las páginas web. Se venden ingentes cantidades de dispositivos que no son los clásicos ordenadores como tablets, smartphones, smart TV.. Los visitantes de una web necesitan que la página sea compatible con todo tipo de dispositivos. Bootstrap de Twitter está muy de moda, a fecha de hoy van por la versión 3.1.1 así que aprovechando para ponerme al día les dejo éste post. Al grano..
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Probando Bootstrap Twitter</title>
</head>
<body>
</body>
</html>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https:///netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
Ésto simplemente enlaza a los archivos públicos que tenemos en Internet. Te ahorras espacio usando los CDN, pero dependes de que dichos archivos estén disponibles para que tu web funcione correctamente.
<nav class="navbar navbar-default" role="navigation">
<!-- El logotipo y el
icono
que despliega el menú se
agrupan
para mostrarlos mejor en
los
dispositivos móviles -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse"
data-target=".navbar-ex1-collapse">
<span class="sr-only">Desplegar navegación</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Logotipo</a>
</div>
<!-- Agrupar los enlaces
de
navegación, los
formularios y
cualquier
otro elemento que se
pueda
ocultar al minimizar la
barra
-->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="#">Enlace #1</a>
</li>
<li>
<a href="#">Enlace #2</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Menú #1 <b class="caret"></b> </a>
<ul class="dropdown-menu">
<li>
<a href="#">Acción #1</a>
</li>
<li>
<a href="#">Acción #2</a>
</li>
<li>
<a href="#">Acción #3</a>
</li>
<li class="divider">
</li>
<li>
<a href="#">Acción #4</a>
</li>
<li class="divider">
</li>
<li>
<a href="#">Acción #5</a>
</li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Buscar">
</div>
<button type="submit" class="btn btn-default">
Enviar
</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#">Enlace #3</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Menú #2 <b class="caret"></b> </a>
<ul class="dropdown-menu">
<li>
<a href="#">Acción #1</a>
</li>
<li>
<a href="#">Acción #2</a>
</li>
<li>
<a href="#">Acción #3</a>
</li>
<li class="divider">
</li>
<li>
<a href="#">Acción #4</a>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
<h1>¡Hola Mundo!</h1>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
¡Hola Mundo!
</div>
<div class="panel-body">
Contenido del panel
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">¡Hola Mundo!</h2>
</div>
<div class="panel-body">
Contenido del panel
</div>
</div>
</div>
Ahora es el momento de probar con un móvil el fichero, o con una tablet. Si quieres verlo claro directamente en el ordenador puedes estrechar horizontalmente el navegador con la página cargada y verás cómo se modifica el aspecto.
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-theme.min.css">
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
Mira que las rutas relativas a los archivos sean correctas y que dichos archivos estén en su sitio, sino no funcionará.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Probando Bootstrap Twitter</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-theme.min.css">
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<!-- El logotipo y el
icono
que despliega el menú se
agrupan
para mostrarlos mejor en
los
dispositivos móviles -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse"
data-target=".navbar-ex1-collapse">
<span class="sr-only">Desplegar navegación</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Logotipo</a>
</div>
<!-- Agrupar los enlaces
de
navegación, los
formularios y
cualquier
otro elemento que se
pueda
ocultar al minimizar la
barra
-->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="#">Enlace #1</a>
</li>
<li>
<a href="#">Enlace #2</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Menú #1 <b class="caret"></b> </a>
<ul class="dropdown-menu">
<li>
<a href="#">Acción #1</a>
</li>
<li>
<a href="#">Acción #2</a>
</li>
<li>
<a href="#">Acción #3</a>
</li>
<li class="divider">
</li>
<li>
<a href="#">Acción #4</a>
</li>
<li class="divider">
</li>
<li>
<a href="#">Acción #5</a>
</li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Buscar">
</div>
<button type="submit" class="btn btn-default">
Enviar
</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#">Enlace #3</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Menú #2 <b class="caret"></b> </a>
<ul class="dropdown-menu">
<li>
<a href="#">Acción #1</a>
</li>
<li>
<a href="#">Acción #2</a>
</li>
<li>
<a href="#">Acción #3</a>
</li>
<li class="divider">
</li>
<li>
<a href="#">Acción #4</a>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
<h1>¡Hola Mundo!</h1>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
¡Hola Mundo!
</div>
<div class="panel-body">
Contenido del panel
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">¡Hola Mundo!</h2>
</div>
<div class="panel-body">
Contenido del panel
</div>
</div>
</div>
</body>
</html>
Sólo me queda decir que los ejemplos de componentes que he puesto los he sacado de la documentación oficial, en ella estan todos los que hay disponibles. Como por ejemplo el uso de rejillas, las fuentes para los pequeños gráficos incluidos, y un largo etcétera..
Saludos!
En éstos días he estado viendo cómo se puede medir la calidad de una página web. Desde un punto de vista se puede medir la cantidad y calidad de los algoritmos que trabajan en la parte servidor, sus funciones o servicios que dan. Por otro lado tenemos las validaciones de la parte cliente, si cumple con los estándares de los lenguajes..
«Por qué no construir una nueva herramienta para encontrar y elegir los iconos sin límites, una herramienta que puede funcionar bien en cualquier proyecto, con el tiempo.»
Parece ser que están dando guerra las tablas xD A continuación les dejo un post con un ejemplo de tabla con cajas seleccionables y otros valores que son en éste caso enteros. Luego tenemos un botón con el que podemos recorrer la tabla (el modelo que representa los valores de la tabla) e ir borrando las filas que tienen la caja seleccionable seleccionada, valga la redundancia…
package swing;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
public class TablaSeleccionable extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JTable table;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TablaSeleccionable frame = new TablaSeleccionable();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public TablaSeleccionable() {
setTitle("Tabla seleccionable by jnj");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 604, 347);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(10, 11, 560, 227);
getContentPane().add(scrollPane);
String[] columnas = { "Los checkbox", "Un dato", "Otro dato" };
final DefaultTableModel modelo;
modelo = new DefaultTableModel(null, columnas);
// hay que programar nuestra propia función getColumnClass para
// poder poner controles como por ejemplo el checkbox con los datos
// booleanos
table = new JTable(modelo) {
private static final long serialVersionUID = 1L;
@Override
public Class getColumnClass(int column) {
switch (column) {
case 0:
return Boolean.class;
case 1:
return Integer.class;
case 2:
return Integer.class;
default:
return Boolean.class;
}
}
};
table.setBounds(26, 26, 463, 332);
scrollPane.setViewportView(table);
// INSERTA UNOS DATOS EN LA TABLA
for (int i = 0; i < 10; i++) {
Object[] nuevafila = { false, i, i };
modelo.addRow(nuevafila);
}
// BOTON QUE ELIMINA FILAS,
// Recorre de atrás hacia adelante las filas eliminando las
// seleccionadas.
JButton btnEliminarFilas = new JButton("Eliminar filas");
btnEliminarFilas.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
for (int i = 9; i >= 0; i--) {
// si la fila i, columna 0 es true, es porque está
// seleccionado el checkbox, entonces borra
if (((Boolean) modelo.getValueAt(i, 0)) == true) {
modelo.removeRow(i);
}
}
}
});
btnEliminarFilas.setBounds(10, 249, 201, 23);
contentPane.add(btnEliminarFilas);
}
}
Siguiendo los minitutoriales de la serie Swing, he desarrollado éste post sobre tablas para aprender a usar un poco más las tablas. Vamos al grano.
Introducción
Es necesario:
Trato de explicar cómo poner cajas seleccionables (checkbox), listas desplegables (combobox) y cajas de texto formateado, en una tabla. En el ejemplo he puesto una caja que sólo admite un número de 3 dígitos. Más adelante lo vemos.
Para manejar los datos puedes usar directamente el modelo de datos que autogenera un JTable, puedes usar un AbstractTableModel como aparece en muchos de los tutoriales que he encontrado por Internet, o puedes usar una implementación de DefaultTableModel, que internamente usa un vector con el que te permite añadir o borrar filas de forma dinámica. Yo he usado un DefaultTableModel que me ha parecido más interesante porque muchas veces necesitamos añadir o borrar datos de una tabla.
El código fuente
package swing;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.text.MaskFormatter;
public class JavaSwing10JTable2 extends JFrame {
private static final long serialVersionUID = 1L;
private JTable tabla;
private static MiModelo modelo;
// el main...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JavaSwing10JTable2 frame = new JavaSwing10JTable2();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// constructor de la ventana, aquí hace todo
public JavaSwing10JTable2() {
setTitle("Java Swing 9 El JTable 2");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 846, 331);
getContentPane().setLayout(null);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(10, 11, 810, 271);
getContentPane().add(scrollPane);
// usa mi modelo de datos
modelo = new MiModelo();
// crea la tabla con mi modelo
tabla = new JTable(modelo);
//
scrollPane.setViewportView(tabla);
// EDITORES PERSONALIZADOS EN LA TABLA
// la caja seleccionable, el checkbox lo pone sólo
// con la función getColumnClass
TableCellEditor editor;
// configura el combo
JComboBoxcomboBox = new JComboBox ();
comboBox.addItem("Opción A");
comboBox.addItem("Opción B");
comboBox.addItem("Opción C");
// configura el campo de texto
JFormattedTextField campoTexto = null;
try {
// sólo admite 3 números
campoTexto = new JFormattedTextField(new MaskFormatter("###"));
} catch (Exception e) {
System.out.println(e.getMessage());
}
// combo a la columna 3
editor = new DefaultCellEditor(comboBox);
tabla.getColumnModel().getColumn(3).setCellEditor(editor);
editor = new DefaultCellEditor(campoTexto);
tabla.getColumnModel().getColumn(4).setCellEditor(editor);
// capturador de eventos del ratón en la tabla
// autogenerado con Eclipse
tabla.addMouseListener(new MouseListener() {
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
int fila = tabla.rowAtPoint(e.getPoint());
int columna = tabla.columnAtPoint(e.getPoint());
JOptionPane.showMessageDialog(null, "Valor de la celda: "
+ tabla.getValueAt(fila, columna).toString());
}
});
}
// modelo de datos, usa DefaultTableModel que internamente
// tiene un vector que nos permite por ejemplo añadir una fila
// fácilmente con addRow...
private static class MiModelo extends DefaultTableModel {
private static final long serialVersionUID = 1L;
private static final String[] nombresColumna = { "ID", "Un decimal",
"Caja seleccionable", "Lista desplegable",
"Campo texto formateado" };
Class[] tiposColumna = { Integer.class, Double.class, Boolean.class,
JComboBox.class, JFormattedTextField.class };
// constructor de la clase, simplemente pone unos datos en el vector de
// datos.
public MiModelo() {
super(nombresColumna, 0);
// carga unos datos iniciales...
for (int i = 0; i < 10; i++) {
Object[] aux = { i, Math.random(), i % 2 == 0, "Opción A",
"123" };
addRow(aux);
}
}
@Override
public Class getColumnClass(int columnIndex) {
// necesario para que dibuje la tabla con los tipos de columna
return tiposColumna[columnIndex];
}
}
}
También he añadido la captura de eventos del ratón. Al escribir tabla.addMouseListener(new Mouse… podemos usar la combinación de teclas Ctrl + espacio y Eclipse nos intentará autogenerar el código. Ésto mismo se puede usar en cualquier momento, si Eclipse detecta que estás escribiendo algo que conoce te lo pone en un listado o si sabe seguro lo que vas a escribir lo autogenera. Es un comportamiento muy útil.
Explicaciones
He usado un DefaultTableModel que internamente tiene un vector de filas, para poder usar la función addRow() y así simplificar todo un poco que para eso está la clase DefaultTableModel.
La historia de configurar la tabla está en la clase MiModelo, espero haber dejado lo bastante comentado el código como para no explicar de nuevo aquí lo que hace. De todas formas si queda duda, mensaje aquí abajo que lo contesto en cuanto pueda.
Podemos obtener el vector de datos de la forma:
Vector data = modelo.getDataVector();
Hay muchas funciones interesantes que nos permitirán hacer lo que necesitemos. Para hacerse una idea, en mi Eclipse pongo modelo. y se me despliega el listado de funciones que tengo disponibles:
Tenemos la que ya he dicho addRow para añadir una fila, getRowCount que devuelve el número de filas, y muchas más. Algunas funciones necesitan un post entero para explicarlas así que lo dejaré aquí.
Donde pone tabla.addMouseListener(… está todo lo relacionado con el ratón. Podemos programar acciones para cuando se hace click, cuando se aprieta el botón, cuando se libera, etcétera…
Sobre los editores personalizados en una tabla hay que decir que admite cajas chequeables, listas desplegables y campos de texto. Además se pueden crear nuevos editores, como por ejemplo el que cita en la documentación oficial de un editor de celda que nos permite elegir entre una tabla de colores.
Más información
Me remito como siempre a la documentación oficial:
De todas formas hay muy buena información en otras páginas web, espero que si has leido éste tutorial por lo menos te haya aclarado algo, o te sirva el código fuente.
No dudes en dejar un mensaje.
Saludos.
Curioseando un programa que utilizo últimamente, el PDF24 Creator, además de en Windows he visto que tenía un Gadget para añadir en Blogger, es decir aquí. Así que lo he puesto en el menú para poder descargar una versión en PDF de lo que sea que esteis viendo en el blog. Lo teneis en la sección PDF para generar un fichero si es que te quieres guardar algo de lo que publico aquí.
Es un programa de Windows, 100% gratuito según reza en su página web donde lo teneis en descarga:
Funciona muy bien, te escanea documentos y los guarda a PDF de manera muy simple, puedes crear documentos de varias páginas, también instala una impresora virtual con la que puedes imprimir desde cualquier programa y te genera un documento. En fin, lo que he probado funciona muy bien y me ha sido muy útil. Todo un complemento digno de mención, y 100% gratis.
Lo único que le hecho en falta, aunque no lo he necesitado mucho la verdad, es un editor PDF con el que se puedan editar el contenido de un documento. Eso sí, a fecha de hoy lo que se puede hacer es mover las páginas de orden, de un documento a otro, pero no se puede editar una página en sí.
Buena herramienta de todas formas, seas programador o no. ¿He comentado que es 100% gratis?
Saludos.
Siguiendo con los minitutoriales de Swing, ha llegado la madre del cordero del manejo de datos. Tarde o temprano necesitamos visualizar, modificar, borrar o guardar datos. Podemos hacer formularios, recorrer tablas guardadas en variables ocultas, usar listas, al final la manera más simple de mostrar muchos de éstos datos es una tabla, que en Swing se llama JTable.
Para seguir éste tutorial hace falta, un entorno de desarrollo (por ejemplo Eclipse), el JDK o el JRE, y un poco de paciencia xD
Si eres un máquina entonces descárgate directamente el código y no te olvides de visitarme de vez en cuando, a ver si aumentan las visitas =)
A saber
Antes de empezar hay que saber que un JTable es un componente Swing, que va a visualizar unos datos que se almacenan internamente en una variable de tipo TableModel. No se recomienda usar directamente pero si no lo necesitamos para qué vamos a complicarnos más la vida en usar otros métodos. Lo he usado para no complicar el ejemplo.
En el ejemplo se va a usar un DefaultTableModel el cual va a guardar referencia del TableModel que tiene la tabla. Entonces cuando se modifica, en éste caso añadiendo más filas, automáticamente la tabla se redibuja y muestra los datos actualizados. Se recomienda usar un AbstractTableModel para manejar los datos pero eso es otro tema.
Comenzando
Gran parte del código se puede generar con Eclipse. Así que los pasos a seguir para emepezar son:
Hecho ésto hago doble click en el botón y me muestra todo el código recién generado listo para seguir implementando.
El código
A continuación el código lo más legible que he podido poner:
package swing;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class JavaSwing9JTable extends JFrame {
// versión
private static final long serialVersionUID = 1L;
// la tabla
private JTable table;
// el modelo de tabla, aquí van a estar los datos.
private DefaultTableModel model;
// función principal
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JavaSwing9JTable frame = new JavaSwing9JTable();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// constructor del frame que contruye toda la ventana...
public JavaSwing9JTable() {
//título
setTitle("Java Swing 9 El JTable");
// cuando cerramos la ventana se cierra la aplicación por completo
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// dimensiones y posición
setBounds(100, 100, 596, 331);
// establece una capa absoluta para posicionar los elementos donde queramos
getContentPane().setLayout(null);
// el panel con barras de scroll automáticas
JScrollPane scrollPane = new JScrollPane();
// dimensiones y posición del panel de scroll
scrollPane.setBounds(10, 11, 560, 227);
// se añade el panel de scroll a la ventana
getContentPane().add(scrollPane);
// nombre de las columnas
String[] columnNames = { "ID", "Un dato", "Otro dato" };
// creo un modelo de datos, sin datos por eso 'null' y establezco los
// nombres de columna
model = new DefaultTableModel(null, columnNames);
// creo la tabla con el modelo de datos creado
table = new JTable(model);
// se pone la tabla en el scroll
scrollPane.setViewportView(table);
// código del botón
JButton btnAadirLnea = new JButton("Meter contenido");
btnAadirLnea.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// aquí se añaden datos a la tabla
for (int i = 0; i < 100; i++) {
// creo un vector con una fila
Object[] aux = { i, i * 34, Math.random() };
// añado la fila al modelo
model.addRow(aux);
}
}
});
// dimensiones y posición del botón
btnAadirLnea.setBounds(10, 249, 267, 23);
// pongo el botón en la ventana
getContentPane().add(btnAadirLnea);
}
}
Aclaraciones
Ésta es la forma más simple que he encontrado de usar una tabla de Swing. El paso siguiente es usar un AbstractTableModel, crearnos una clase que la implemente, con la que podemos entonces hacer cosas como meter cajas seleccionables o cuadros desplegables en las filas.
Más información
Hay mucha información en Internet, me remito primero a la documentación oficial, está en inglés y es muy extensa:
Espero que sirva. En próximos posts espero ver cómo controlar los clicks del ratón, esos temas que citaba de la clase AbstractTableModel, formularios maestro-detalle, etcétera…
Saludos!
Podemos ver por todos lados la palabra Bean, significa vaina en inglés. Se puede leer en los lugares más variopintos: desde el NetBeans, los Java Beans, los Beans en Visual Studio, Eclipse, los Enterprise Java Beans, los Beans que usamos para acceder y guardar datos de nuestros programas.. pero ¿qué es un Bean de Java?
Continuar leyendo..