Mostrando las entradas con la etiqueta tecnologia. Mostrar todas las entradas
Mostrando las entradas con la etiqueta tecnologia. Mostrar todas las entradas

10 may 2010

Servicios Web – Retornar un XML con NuSOAP

Aquí estoy nuevamente con un artículo de web services / servicios web, en esta oportunidad, voy a incluir un ejemplo en el cual ser servidor retorna un XML al cliente, y este lo muestra por pantalla. Este ejemplo surgió a petición de un visitante a mi blog. Espero que les sea de utilidad.

Servidor

El servidor es muy similar a todos los servidores de los ejemplos anteriores, simplemente que retorna un XML. Dado que el servidor retorna un XML, debemos convertirlo antes de poder retornarlo, para que no genere conflictos con el código del “envoltorio” (SOAP) que transportará el XML. La forma más rápida es utilizar el método base64_encode que ya trae incorporado PHP. Es decir, codificamos el XML con base64, de esta forma nos aseguramos que no aparecerán caracteres extraños, y luego retornamos ese XML modificado.

<?php
require_once('nusoap/lib/nusoap.php');
$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap_ejXML';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;

$server->register('getXML', // Nombre de la funcion
                   array(), // Parametros de entrada
                   array('return' => 'xsd:string'), // Parametros de salida
                   $miURL);
function getXML(){
    return new soapval('return', 'xsd:string', base64_encode('<?xml version="1.0" encoding="UTF-8"?>
<vehiculos>
  <coche>
      <marca>Toyota</marca>
      <modelo>Corolla</modelo>
      <fechaCompra>2002</fechaCompra>
  </coche>
  <coche>
      <marca>Honda</marca>
      <modelo>Civic</modelo>
      <fechaCompra>2003</fechaCompra>
  </coche'));
}

$server->service($HTTP_RAW_POST_DATA);
?>

En el ejemplo el XML es fijo y esta incorporado a la función del web services, en la vida real, el código XML lo generaremos a partir de algunos datos dinámicos o en como necesitemos.

Cliente

El cliente es casi el mismo que en los ejemplos iniciales que he dado en mi blog, salvo que antes de mostrar/procesar la respuesta, se debe transformar de base64 a texto normal, algo así como “descodificarla”. Para esto PHP también nos provee de una función base64_decode.

<?php
require_once('nusoap/lib/nusoap.php');
// Crear un cliente apuntando al script del servidor (Creado con WSDL)
$serverURL = 'http://pruebas.orlandobrea.com.ar';
$serverScript = 'nusoap_server_ejXML.php';
$metodoALlamar = 'getXML';

$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
    echo '<pre style="color: red">' . $error . '</pre>';
    echo '<p style="color:red;'>htmlspecialchars($cliente->getDebug(), ENT_QUOTES).'</p>';
    die();
}

// 1. Llamar a la funcion getRespuesta del servidor
$result = $cliente->call(
    "$metodoALlamar",                     // Funcion a llamar
    array(),    // Parametros pasados a la funcion
    "uri:$serverURL/$serverScript",                   // namespace
    "uri:$serverURL/$serverScript/$metodoALlamar"       // SOAPAction
);
// Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
if ($cliente->fault) {
    echo '<b>Error: ';
    print_r($result);
    echo '</b>';
} else {
    $error = $cliente->getError();
    if ($error) {
        echo '<b style="color: red">-Error: ' . $error . '</b>';
    } else {
        echo base64_decode($result);


    }
}

?>

En el ejemplo, solo muestro la respuesta que me ofreció el servidor del servicio web, cuando estemos ante una implementación real, seguramente deberemos procesar esta información para ofrecer, por ejemplo, una respuesta a un usuario.

Conclusión

Como hemos podido ver, la implementación de este tipo de alternativas, es muy sencilla, solo debemos tener especial cuidado en la codificación de la respuesta que utilizamos en NuSOAP. A modo de “regla” podría decir que todo aquello que sabemos que utilizará caracteres extraños, podemos codificarlo en base 64 en el servidor, y luego decodificarlo en el cliente (de esta forma nos aseguramos que la información ha viajado correctamente).

Debo reconocer que hace tiempo que no indago el código de NuSOAP, por lo tanto no recuerdo si codifica de alguna forma las respuestas automáticamente (que yo recuerde, no lo hace).

Espero que haya sido de utilidad, y que sirva para que sigan explorando con los Web Services.

Saludos a todos los seguidores, y gracias!

3 may 2010

WebServices con NuSOAP en PHP - Ejemplo 4 - Vista

Bueno, estoy volviendo al blog de a poco, es por eso que estoy publicando la 4 parte de los ejemplos de web services con NuSOAP. En esta oportunidad voy a poner un simple ejemplo de cliente/servidor en el cual el cliente muestra la información enviada por el servidor.

Si bien el código es muy sencillo, y parecido a los ejemplos anteriores, puede ser un poco más complejo de seguir, ya que hay una parte de visualización de los datos obtenidos desde el servidor (a diferencia de los ejemplos anteriores).

Es importante aclarar que esta forma de desarrollo no es la más aconsejable para un desarrollo real, ya que la vista esta muy ligada al código de conexión. Todo mejoraría un poco más utilizando algún sistema de templates, como smarty, twig, etc.

¿Como funciona el ejemplo?
El cliente llama a listarClientes en el servidor, y muestra el resultado retornado en una tabla. Cuando el usuario hace click para ver los datos de un determinado cliente, el cliente (PHP) llama a getCliente, que retorna todos los datos del cliente. Luego, muestra todos los datos obtenidos del servidor al usuario.

Comencemos por el servidor

<?php

require_once('nusoap/lib/nusoap.php');
require_once('cliente.php');
require_once('clienteDAO.php');
require_once('soporte_obrea.php');

$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;

$soporteWS = new SoporteWS(); // Creo una instancia del objeto propio (SoporteWS)

// ---------------- Funciones usadas por el WS
function getCliente($id) {
    global $soporteWS;
    $dao = new ClienteDAO();
    $cliente = $dao->getCliente($id);
    $respuesta = $soporteWS->convertirAVectorParaWS($cliente);
    return new soapval('return', 'tns:Cliente', $respuesta);
}
function listarClientes() {
    $dao = new ClienteDAO();
    $listado = $dao->getList();
    $objSoporte = new SoporteWS();
    $respuesta = $objSoporte->convertirAVectorParaWS($listado);                 
    return new soapval('return', 'tns:listadoClientes', $respuesta);
}

// --------------- Definicion de las funciones y estructuras expuestas en el WS
$server->wsdl->addComplexType('Cliente',
    'complexType',
    'struct',
    'all',
    '',
    array(
        'id' => array('name' => 'id', 'type' => 'xsd:int'),
        'nombre' => array('name' => 'nombre',    'type' => 'xsd:string'),
        'apellido' => array('name' => 'apellido', 'type' => 'xsd:string'),
        'cuit' => array('name' => 'CUIT',    'type' => 'xsd:string')
    )
);
$server->wsdl->addComplexType('listadoClientes',
    'complexType',
    'array',
    '',
    '',
    array (array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:Cliente[]'))
);

$server->register('getCliente', // Nombre de la funcion
                   array('id' => 'xsd:int'), // Parametros de entrada
                   array('return' => 'tns:Cliente'), // Parametros de salida
                   $miURL
                 );
$server->register('listarClientes', // Nombre de la funcion
                   array(), // Parametros de entrada
                   array('return' => 'tns:listadoClientes'), // Parametros de salida
                   $miURL
                 );

$server->service($HTTP_RAW_POST_DATA);
?>


El servidor es muy similar a los anteriores, expone solo dos métodos:

  • listarClientes: retorna una lista con los clientes.
  • getCliente: retorna el cliente cuyo id es recibido como parámetro de llamada.
La definición del objeto, y los métodos de acceso a datos (DAO) están definidos en los archivos que son importados (cliente.php y clienteDAO.php). Recomiendo que te descargues el archivo comprimido con todo los archivos, que se encuentra al final de este tutorial y que prestes atención a estos archivos. Si bien son muy sencillos, te ayudará mucho que vayas realizando pruebas sobre los mismos también para que puedas ver el comportamiento. Si solo te interesa la parte de WS (Web Services), puedes prestar atención solo a los archivos que están publicados.

¿Como es el cliente?
Bueno, el cliente es un poquito más complejo que los anteriores, ya que no solo hace peticiones al servidor, sino que tiene una pequeña interacción y presenta los datos al usuario.


<?php
require_once('nusoap/lib/nusoap.php');
require_once('cliente.php');
require_once('soporte_obrea.php');

// Crear un cliente apuntando al script del servidor (Creado con WSDL)
$serverURL = 'http://pruebas.orlandobrea.com.ar'; // Reemplazar por la URL de tu servidor
$serverScript = 'nusoap_server_ej4_view.php';
$soporteWS = new SoporteWS();

$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
    echo '<pre style="color: red">' . $err . '</pre>';
    echo '<p style="color:red;'>htmlspecialchars($client->getDebug(), ENT_QUOTES).'</p>';
    die();
}

$listado = '';
$objCliente = '';
// Existe el parametro "view" en la URL con la cual se esta llamando a este script?
if ($_GET['view']) {
    // Si => debo mostrar los datos del cliente
    $metodoALlamar = 'getCliente';
    $result = $cliente->call(
        $metodoALlamar,                     // Funcion a llamar
        array($_GET['id']),    // Parametros pasados a la funcion
        "uri:$serverURL/$serverScript",                   // namespace
        "uri:$serverURL/$serverScript/$metodoALlamar"       // SOAPAction
    );
    // Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
    if ($cliente->fault) {
        echo '<b>Error: ';
        print_r($result);
        echo '</b>';
    } else {
        $error = $cliente->getError();
        if ($error) {
            echo '<b style="color: red">Error: ' . $error . '</b>';
        } else {
            $objCliente = $soporteWS->convertirDeVectorDesdeWS($result, 'Cliente');
         }
    }
} else { // No fue llamado con el parametro "view" en la URL => debo mostrar el listado de clientes
    $metodoALlamar = 'listarClientes';
    $result = $cliente->call(
        $metodoALlamar,                     // Funcion a llamar
        array(),    // Parametros pasados a la funcion
        "uri:$serverURL/$serverScript",                   // namespace
        "uri:$serverURL/$serverScript/$metodoALlamar"       // SOAPAction
    );
    // Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
    if ($cliente->fault) {
        echo '<b>Error: ';
        print_r($result);
        echo '</b>';
    } else {
        $error = $cliente->getError();
        if ($error) {
            echo '<b style="color: red">Error: ' . $error . '</b>';
        } else {
            $listado = $soporteWS->convertirDeVectorDesdeWS($result, 'Cliente');
         }
    }
}
// De aca en mas es la pagina HTML que muestra el listado de clientes o los datos de un cliente en particular (dependiendo
// si se llamo con view como parametro o no)
?>
<html>
<body>
    <? if ($listado) { ?>
    <!-- Listado de todos los clientes -->
    <h1>Listado</h1>
    <table>
        <tr><td>Nombre</td><td>Apellido</td><td>CUIT</td><td>Acciones</td></tr>
        <?php
            foreach ($listado as $objeto) {
                echo '<tr>';
                echo '<td>'.$objeto->nombre.'</td>';
                echo '<td>'.$objeto->apellido.'</td>';
                echo '<td>'.$objeto->cuit.'</td>';
                echo '<td><a href="nusoap_client_ej4_view.php?view=yes&id='.$objeto->id.'">Ver</a></td>';
                echo '</tr>';
            }
        ?>
    </table>
    <? } else { ?>
    <!-- Datos del cliente seleccionao -->
    <h1>Cliente</h1>
    <?php
        echo '<strong>ID:</strong> '.$objCliente->id.'<br/>';
        echo '<strong>Nombre:</strong> '.$objCliente->nombre.'<br/>';
        echo '<strong>Apellido:</strong> '.$objCliente->apellido.'<br/>';
        echo '<strong>CUIT:</strong> '.$objCliente->cuit.'<br/>';
    ?>
    <? } ?>
</body>
</html>


Como mencione anteriormente, el cliente es un poco más complejo que los anteriores, ya que el mismo script permite ver el listado de clientes, y los datos de un cliente en particular.
Si el script, no recibe parámetros en la URL, muestra el listado de todos los clientes que se encuentran en el sistema (llamando a listarClientes en el WS). En cambio si recibe el parámetro view, y el id del cliente que se desea mostrar, ejecuta otra sección del script, la que muestra los datos del cliente.

La página del listado se ve así












Y luego de hacer click sobre "Ver", nos lleva a la página:











la cual contiene el detalle del cliente seleccionado, en este caso "Blas Pascal". Ambas páginas, se encuentran en el mismo script. Es cuestión de gustos incluir ambas en el mismo script, o utilizar dos scripts diferentes. A los fines de este artículo, me pareció más práctico incluir toda la información del cliente en un único script. Si lo deseas, puedes separar el archivo cliente en dos scripts menores.

Los archivos cliente, y servidor los puedes descargar aquí

Conclusión
Este artículo, es una recopilación de los anteriores, con unos pequeños agregados, como la interacción con el usuario, y la presentación de la información.

Este artículo pretende ser el puntapié inicial para que comiences a explorar tu mismo con los servicios web, ya sean en PHP o cualquier otro lenguaje que te guste. Las posibilidades son infinitas, tu serás el que ponga los limites en que aplicación o módulo utilizarás web services. Ya tienes una base para seguir explorando, ahora depende mucho de ti que practiques y comiences a utilizarlos en tus proyectos.

Debo hacer una aclaración que creo que es importante si aún no has comenzado proyectos con Web services. Al comenzar a utilizar web services, te encontrarás con varios inconvenientes, algunos de la herramienta que utilizamos, y otros propios de la etapa de aprendizaje. Lo importante es saber que el camino puede ser un poco difícil en algunos momentos, pero se puede lograr. Si te quedas atascado, sigue probando, descansa un poco, piensa alternativas, busca las posibles causas, y vuelve a retomar. Por eso es tan importante, que no comiences con un proyecto real, sino que, como todo, comiences sobre un proyecto interno, o de prueba, para ir probando y tener tiempo para encontrar los trucos.

Otro punto importante, los Web Services, tampoco son la bala de plata (Silver bullet), por lo tanto, no intentes utilizarlos para cualquier proyecto, aplica tu criterio para decidir que es lo más conveniente para cada proyecto o módulo que debes realizar.

Espero que haya sido de utilidad, me gustaría conocer tu experiencia, si te ha resultado de utilidad este artículo, y que información más te gustaría encontrar sobre estos temas.

Éxitos y a seguir probando.

10 dic 2009

¿Google Wave puede desplazar a Evernote?

Hoy, como todos los días, abrí EVERNOTE para guardar algunas notas y poder mantenerlas en un lugar independiente de la computadora que utilice, y en ese momento pense... ¿por que no uso Google Wave para esto? la respuesta es sencilla, aún no hay una interfaz de escritorio que me permita administrar la información como EVERNOTE.
Sería una muy buena alternativa el poder hacer una interfaz web que sea multiplataforma, al estilo EVERNOTE, pero utilizando Google Wave como backend. Aún no he investigado el protocolo de Wave, se que se puede hacer sin mayores complicaciones, pero por el momento no dispongo del tiempo para hacerlo.
Desde mi punto de vista, los beneficios que se podrían lograr utilizando Google Wave al estilo EVERNOTE, son lo siguientes:
  • Posibilidad de incluír extensiones a la información subida.
  • Posibilidad de compartir la información con otras personas dentro de Wave (incluír en el Wave a otros usuarios).
  • Google Wave tendrá mucha más integración con otras aplicaciones, que EVERNOTE.
  • Google Wave es totalmente gratuito, independientemente de la cantidad de información subida/descargada.
  • La búsqueda se hace con Google.
Como no todo es maravilloso en la vida, aún faltan mejorar los siguientes puntos:
  • Posibilidad de compartir un Wave publicamente.
  • Que Google Wave sea un poco más estable (he tenido problemas con Waves largos).
  • Que se pueda desarrollar una aplicación multiplataforma para la administración de Waves.
  • Que se pueda hacer más amigable la interfaz de Wave para este tipo de información.
Por el momento, deberemos seguir esperando a ver como se van desarrollando los hechos con Google Wave. Como sabemos Google Wave esta en beta aún, y tiene varias cosas por mejorar, pero estoy convencido que en el futuro tiene muchas posibilidades de ofrecer beneficios realmente interesantes... esto que planteo, será uno de ellos? paciencia.

1 dic 2009

Como transferir archivos con NuSOAP

Gracias al comentario de un visitante a mi blog, me dí cuenta que no había incluído ejemplos de como transferir archivos mediante un servicio web, más precisamente utilizando NuSOAP.
Por lo tanto hoy realice un ejemplo muy sencillo que nos permite hacer precisamente eso, tener un servidor corriendo NuSOAP, y que un cliente realice una petición y el servidor retorne el archivo solicitado por el cliente.

Servidor para transferir archivos

El servidor es muy similar al ejemplo que utilice en el primer post que realice en mi blog sobre este tema (http://www.orlandobrea.com.ar/2009/09/webservices-con-nusoap-en-php-ejemplo-1.html). Voy a ir directamente al ejemplo, ya que todo lo necesario para entender el mismo ya lo he explicado en los posts anteriores.
// Archivo nusoap_server_ejFile.php
<?php
require_once('nusoap/lib/nusoap.php');
$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap_ej1';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;




$server->register('getArchivo', // Nombre de la funcion
 array('nombre' => 'xsd:string'), // Parametros de entrada
 array('return' => 'xsd:string'), // Parametros de salida
 $miURL);
// Función que recibe el nombre del archivo que debe retornar.
// Lee el contenido del archivo cuyo nombre corresponde con el pasado
// como parámetro a la función, le aplica Base64 para codificar su
// contenido, y luego lo retorna como un string
function getArchivo($nombre){
 $archivo = file_get_contents($nombre);
 $archivo = base64_encode($archivo);
 return new soapval('return', 'xsd:string', $archivo);
}

$server->service($HTTP_RAW_POST_DATA);
?>

El cliente que recibe los archivos

El cliente también es muy similar al del primer ejemplo, por lo tanto ya vamos a lo importante, el código.
// Archivo nusoap_client_ejFile.php
<?php
require_once('nusoap/lib/nusoap.php');
// Crear un cliente apuntando al script del servidor (Creado con WSDL)
$serverURL = 'http://pruebas.orlandobrea.com.ar';
$serverScript = 'nusoap_server_ejFile.php';
$metodoALlamar = 'getArchivo';

$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
 echo '<pre style="color: red">' . $error . '</pre>';
 echo '<p style="color:red;'>htmlspecialchars($cliente->getDebug(), ENT_QUOTES).'</p>';
 die();
}

// 1. Llamar a la funcion getArchivo del servidor, pasando como parámetro 'wallpaper_mac.png'
$result = $cliente->call(
    "$metodoALlamar",                     // Funcion a llamar
    array('wallpaper_mac.png'),    // Parametros pasados a la funcion
    "uri:$serverURL/$serverScript",                   // namespace
    "uri:$serverURL/$serverScript/$metodoALlamar"       // SOAPAction
);
// Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
if ($cliente->fault) {
    echo '<b>Error: ';
    print_r($result);
    echo '</b>';
} else {
    $error = $cliente->getError();
    if ($error) {
        echo '<b style="color: red">Error: ' . $error . '</b>';
    } else {
 // Decodificación de la respuesta del servidor
 $contenido = base64_decode($result);
 // Guardar la respuesta del servidor, decodificada, en el archivo tmp.png
 file_put_contents("tmp.png", $contenido);
    }
}

?>
El código es bastante sencillo de enteder, simplemente se llama a getArchivo en el webservice del servidor, y se le pasa como parámetro el archivo que deseamos descargar (en este caso wallpaper_mac.png). El servidor retorna el archivo al cliente (codificado con base64). El cliente recibe el archivo y lo decodifica, luego lo guarda en un archivo "tmp.png".
El ejemplo es muy sencillo, y no es apto para un ambiente productivo. Para poder usar este ejemplo en un servidor productivo, deberíamos:
  • Agregar verificación de errores (Por ejemplo, que el archivo exista).
  • El servidor debe establecer ciertas reglas de seguridad (Por ejemplo que solo se pueda acceder a ciertos archivos)
  • Que el cliente pueda solicitar diferentes tipos de archivos (que el archivo a solicitar no este fijo como en el ejemplo, wallpaper_mac.png).
  • Que el archivo en el cual se guarda el archivo recibido, sea modificable.
Estos son solo algunas correcciones que deberíamos hacer para mejorar el código y que sea más seguro. Para los fines del ejemplo, preferí mantener el mismo lo más sencillo posible, para enfocarnos en lo importante en este caso.
Este ejemplo también esta disponible para su descarga.

25 nov 2009

Querés saber como podes utilizar Google Wave


(Inspirado en textos de Google Wave)

Hoy luego de seguir usando Google Wave, y estar muy contento con las funcionalidades que trae, decidí leer un poco de la documentación del mismo, que esta en Waves.




Empecemos por como funciona Google Wave

Antes de empezar con las ideas de uso de Google Wave, quiero dar una pequeña explicación de como funciona Google Wave, para aquellos que aún no lo probaron.
Google Wave maneja todo por Waves, que son algo así como un lugar donde ingresar y compartir información. Imaginemos un Wave como una hoja en blanco, que podemos crear, editar, borrar, archivar, y por sobre todas las cosas, compartir con otras personas. A diferencia de una hoja en blanco, que solo una persona puede estar asándola a la vez, es que en Google Wave, todos los que elegimos que puedan participar de dicha Wave, pueden editarla a la vez (o en tiempos diferentes).
Bueno, luego de haber hecho una muy breve introducción a que es Google Wave, comenzaré a desarrollar algunas de las ideas de uso de Google Wave (debo aclarar que me estoy inspirando en un texto de Google, no es una autoría mía, ni tampoco es una traducción literal del contenido; más bien es una inspiración para poder desarrollar sobre ella). Bueno, ahora si veamos como podemos usar esa hoja en blanco que nos ofrece Google Wave de forma más productiva que solo mirarla.


Como podés usar Google Wave


Organizar eventos

Mantener las ideas organizadas en un único lugar, como puede ser, el itinerario sugerido, el menú, la administración de los recursos, etc. Además se pueden incorporar el estado del clima, y mapas. Veamos, entonces podríamos organizar un evento, en el cual podríamos hacer el itinerario, si se realiza en diferentes lugares, con solo utilizar Google Maps e incorporarlo a nuestro Wave.


Notas de reuniones

Se puede preparar la agenda de una reunión entre varias personas, cada uno realizando notas, incorporando información, enlazando a documentos anexos (por ejemplo presentaciones PowerPoint), incorporando enlaces a sitios en los cuales podemos recopilar mayor información, etc. Todo esto en tiempo real, cada persona que participa del Wave puede ver los cambios al instante, y también realizar cambios en el momento que lo desee.
Una función muy útil de Google Wave es la llamada "Playback" que nos permite ver como se fue comportando el Wave, quién abrió el wave, que cambios se fueron realizando por cada persona. Es decir tenemos todo el historial, o sea que si por error perdimos información, no debemos preocuparnos porque sigue estando allí para recuperar.
Una vez finalizad la reunión se pueden realizar las notas finales en el mismo Wave o en uno nuevo. Podríamos invitar a todas las personas que participaron en un nuevo Wave, para que nos comenten su experiencia, y podemos incorporar una votación para que puedan calificar a la reunión.


Reportes de grupo y desarrollo de proyectos

Trabajar de manera colaborativa en tiempo real, realizando nuestros primeros bosquejos, discutir y solicitar feedback, todo en el mismo Wave. Nos ahorramos de enviar muchos e-mails con copias a diferentes personas, y luego ya no saber si a todos le enviamos la información que debíamos. A medida que los bosquejos van tomando más forma, se irán convirtiendo en proyectos, que pueden seguir desarrollandose en Wave.
Esta quizás es una de las funciones que yo más utilizo, desarrollo de proyectos, la cual utilizó para ir plasmando los nuevas ideas, y compartirlas con las personas que formarán parte de ella (y viceversa, que personas que tienen ideas, me incorporen a mi para formar parte del desarrollo del proyecto).


Brainstorming / Lluvia de ideas

Es una función que Google Wave permite realizar muy bien , principalmente cuando tenemos un grupo distribuido. Todas las personas pueden participar de un Wave escribiendo todas las ideas que le vienen a la cabeza, y todas en simultáneo.
También podemos ir incorporando imágenes, videos, enlaces externos, y hasta enlazarlos con otras Waves.
Luego ya tenemos toda la información, y solo nos resta comenzar a filtrar las ideas.


Compartir fotos

Se puede arrastrar y pegar (drag & drop) fotos desde el escritorio al Wave. Luego esas fotos se pueden compartir con otras personas. Existe un visor de fotos (slideshow) que nos permite ver todas las fotos de manera mucho más sencilla (es solo sentarnos y apreciarlas). Las demás personas que participan de la Wave también pueden subir sus fotos para que sean visibles por todos los integrantes de la Wave. Es muy sencillo poder hacer un álbum de fotos de un grupo con Google Wave.


Como uso yo Google Wave

Bueno, ya lo comente un poquito en este post, y en posts anteriores. Principalmente lo uso para abrir un Wave por idea, y compartirlo con diferentes personas que van agregando valor a la idea. Por ejemplo si se me ocurre que la venta de peces luminosos (idea extraída de The Big Bang Theory) puede ser buena, abro un Wave sobre este tema e invito a diferentes personas a que opinen sobre ella, y vayan aportando valor, hasta llegar a una idea potable, o descartarla por el momento.


Y ya cerrando este post

Ahora estoy intentando buscar nuevos usos a Google Wave, lo cual lo estoy intentando alcanzar por medio de otras personas que estoy sumando a mis contactos. Entre mis contactos tengo a personas de Marketing, y personas mucho más Técnicas (principalmente sistemas), es muy interesante ver los beneficios que puede traer a los diferentes tipos de personas. Sigamos descubriendo, esa es la única forma de crecer.

23 nov 2009

Google AdWords para sitios específicos


Como hace unos días recibí crédito de Google para abrir mi cuenta de AdWords, decidí comenzar a probar el servicio. Comento un poco mi experiencia anterior y el porque no probé anteriormente el servicio.

El sitio del cual ya hace un tiempo me encargo en administrar y posicionar, es un sitio para un sector especifico, y he logrado una muy buena posición en Google por las palabras claves detectadas. Como de esta forma, me resultaba conveniente y exitoso el posicionamiento del sitio web, me parecía que invertir en AdWords podría ser una perdida de tiempo y dinero. Dado que Google me dio crédito para AdWords, ya tenía 50% de mis excusas resueltas, solo me faltaba tiempo (pero decidí hacermelo para probar el servicio).

De esta forma comence a probar el servicio de AdWords de Google, luego de leer un poco y generar mis primeros avisos me dí cuenta que existian muchas variables en juego al crear una campaña y avisos en AdWords. Voy a comenzar definiendo algunos términos de AdWords para todos aquellos que aún no lo han probado:

Campaña: es un presupuesto asignado a una determinada forma de publicación y anuncios que deseamos publicar. Varios anuncios pueden pertenecer a una misma campaña. En la campaña definimos el costo diario que deseamos utilizar, las regiones en la cuales deseamos que aparezca nuestro aviso (si es solo para un país determinado, grupo de paises, etc.), los dispositivos en los cuales deseamos que sean visualizados nuestros anuncios (laptops, teléfonos, etc), y rango de fechas en las cuales la campaña estará activa.


Anuncio: es un aviso en el cual indicamos el texto/gráfico que deseamos que aparezca en la pantalla de nuestros potenciales. Cada anuncio pertenece a una campaña. Google recomienda que incorporemos más de un anuncio a la campaña para poder medir la efectividad de cada uno de ellos. Todos los anuncios utilizan el mismo presupuesto que la campaña, es decir, si asignamos $10 por día a una campaña, todos los anuncios que pertenecen a la campaña compartirán dicho presupuesto.


¿Cual fué mi experiencia?
Si bien no he realizado una investigación profunda de AdWords, he recopilado cierta información y experiencia con los anuncios que he publicado, la cual voy a detallar en los siguientes puntos:
  • Se debe realizar una elección adecuada del presupuesto a asignar a cada campaña, se debe tener en cuenta que el monto que uno indica que desea utilizar por día, no necesariamente es el que Google AdWords utilizará (hasta el momento AdWords siempre ha destinado mayor presupuesto por día del que le he asignado).
  • Se deben ingresar varios anuncios por cada campaña, intentando orientar cada uno a diferentes beneficios del producto/servicio que estamos intentando promocionar. Esto es útil para poder verificar la efectividad de los diferentes tipos de anuncios.
  • Se debe tener especial cuidado en las redes que incorporamos nuestros anuncios, una mala elección de las redes provocará una alta cantidad de clicks entrantes a nuestro sitio web, pero también una alta tasa de rebote. En un principio deje que Google gestionara las redes en las cuales promocionaba los anuncios, pero esto solo logro tener una muy alta cantidad de clicks (incrementando costos), pero una aún más alta tasa de rebote. O sea que el tráfico generado no pertenecia al público objetivo del anuncio.
  • Utilizar las herramientas de sugerencias de palabras claves, y de búsqueda de redes en las cuales incorporar los anuncios. Ambas herramientas son muy útiles para poder mejorar las visitas a nuestro sitio web.
  • Si tus productos son demasiados espécificos, la campaña debe ser de mayor duración y debe ser promocionada en sitios web que sean relevantes para tus productos/servicios. Mi experiencia indica que AdWords es mucho más útil cuando comercializamos productos o servicios que son masivos y principalmente para usuarios finales, pero cuando ofrecemos productos o servicios mucho más específicos debemos ser mucho más pacientes, y principalmente selectivos en la selección de los parámetros. Por ejemplo, si queremos promocionar una red social, sabemos que nuestro público objetivo es un rango bastante amplio de personas y el objetivo es estar en la mayor cantidad de sitios web posibles. En cambio, cuando hablamos de productos específicos, deseamos estar en sitios más selectivos, y ser más pacientes.
Hasta el momento he encontrado estos puntos como importantes para iniciar nuestras campañas en AdWords. Hasta el momento estoy convencido que es una muy buena herramienta para generar tráfico para sitios que ofrecen productos o servicios bastante masivos, pero no tan convenientes (a menos que modifiquemos bastante las opciones) para sitios mucho más específicos.
Antes de utilizar Google AdWords la tasa de rebote del sitio web que estoy administrando era de apróximadamente el 50%, cuando comencé a utilizar AdWords, la tasa subió a casí el 80%, he tenido que realizar muchas modificaciones a las campañas para intentar lograr que el porcentaje de rebote llegue, nuevamente, a valores de apróximadamente el 50%.
Con el tiempo, ire conociendo mucho más de la herramienta. Pero por ahora, sigo prefiriendo una buena posición en los buscadores (no en enlaces patrocinados / AdWords) y mejorar el posicionamiento por medio de técnicas de SEO tradicionales. Obviamente, las visitas es solo el comienzo del éxito de nuestro sitio web, luego debemos lograr que el visitante tenga una buena experiencia y se interese por lo que tenemos para ofrecerle.

12 nov 2009

Experiencia con el trackman marble de Logitech

Ya hace más de 2 meses que estoy utilizando el trackball de Logitech, más precisamente el Trackman Marble y estoy muy impresionado con lo adictivo que termino siendo el uso del mismo. Como comente en mi post anterior hacia mucho tiempo que no utilizaba un trackball, y los que había utilizado anteriormente eran operados con el dedo pulgar, este nuevo trackball que adquirí se opera con el dedo índice/mayor.

La experiencia fue y es excelente, no voy a realizar una comparativa con respecto a un mouse tradicional, ya que algo de ello ya lo he comentado en mi post anterior. En esta ocasión me voy a enfocar a comentar mi experiencia, y las diferencias con el trackball operado con el dedo pulgar.

Debe confesar que al principio me costo un poco adaptarme nuevamente al uso del trackball, pero luego de uno o dos días, ya me sentía mucho más comodo, y a la semana, ya lo sentía parte de mí. La adaptación consiste en dos partes, primero que debemos operar el mouse con nuestros dedos índice/mayor sin mover demasiado la mano, y la segunda es que debemos acostumbrarnos a presionar el botón derecho del mouse con el dedo pulgar, y el botón izquierdo con el dedo meñique (o el que nos quede más comodo).

Ventajas del trackball operado con el dedo índice/mayor

  • Tenemos mayor fuerza en nuestros dedos medios, y precisión, por lo tanto es mucho más sencillo mover la "bola" del mouse, y lograr una muy buena precisión.
  • Es muy fácil mover el cursor de una esquina de la pantalla a otra, ya que con un impulso importante con los dedos que operamos el mouse, el mismo logra una inercia y "mágicamente" el cursor continúa su desplazamiento.
  • La precisión lograda es mucho mayor que la que recuerdo haber logrado con un trackball operado con el dedo pulgar (quizás sea solo yo, pero no tengo tan buen control de mi dedos pulgares, como de mis dedos centrales).

Y ya cerrando el post...

Mi experiencia con los trackballs es que una vez que comenzas a utilizar uno de ellos, luego es muy difícil poder volver a un mouse tradicional. Como desventaja con respecto a los mouse tradicionales es el espacio que ocupa, es mayor, y por lo tanto es más difícil transportarlo en el, por ejemplo, bolso de la notebook.

Invito a todas las personas que tengan la posibilidad de probar un trackball, estoy seguro que les será de mucha utilidad. La regla, que he comprobado, es que si deseas utilizar el trackball principalmente para jugar, es más conveniente uno operado con el dedo pulgar, en cambio para diseño u operaciones con mayor precisión o uso normal, es más cómodo el trackball operado con el dedo índice/pulgar.

Yo te puedo ofrecer solo mi experiencia, pero al final, vos tenés la última palabra, y el poder de decisión es tuyo, solo debes ser objetivo con lo que lees, y analizar el uso que le deseas dar. Pero ante todo lo importante es "actuar".


11 nov 2009

Google Wave

Luego de esperar un tiempo que me llegue la invitación para probar Google Wave (http://wave.google.com) hoy me llegó la invitación. Aún no he podido realizar demasiadas pruebas, simplemente recorrí un poco la interfaz, con el tiempo ire posteando los avances que voy haciendo sobre las pruebas de este "nuevo" sistema de comunicación.

Creo que puede ser una muy buena alternativa para la realización de proyectos, por ejemplo para administrar contenido que estamos desarrollando. Podemos lanzar una idea o bosquejo al grupo de personas que deben participar, y luego cada uno va realizando sus aportes en el documento. Una vez generado, podemos pasar el contenido a otro sistema, por ejemplo wiki, sitio, o cualquier otra alternativa, o podemos dejarlo en Google Wave.

Siempre es interesante probar las nuevas tecnologías que van apareciendo, para saber si nos es útil para mejorar nuestra forma de trabajo, y/o tenerla en cuenta en el futuro. Cuando uno se acostumbra a las herramientas de colaboración, debo reconocer, que es muy difícil volver.

En el futuro seguire posteando sobre las pruebas y experiencias que vaya teniendo con Google Wave.


WebServices con NuSOAP en PHP - Ejemplo 3

Este el tercer ejemplo que voy a publicar de la comunicación entre el cliente y servidor de WebServices utilizando NuSOAP con un servidor PHP. Para aquellos que llegarón directamente a esta página, les recomiendo que primero lean el primer ejemplo de NuSOAP con PHP, y luego el segundo ejemplo de NuSOAP con PHP, antes de continuar con este. En este ejemplo en particular voy a modificar el ejemplo 2, para en vez de retornar un objeto, retornar una lista de objetos. En el ejemplo anterior, trate un caso muy particular, que es cuando necesitamos obtener un objeto desde el servidor, en este nuevo ejemplo, voy a obtener un listado de objetos. Muchas veces necesitamos un listado de algún tipo de objeto, por ejemplo, un listado de clientes, con este nuevo Servicio Web planteo este caso, en el cual el servidor nos devuelve un listado con todos los clientes que existen en el sistema.
Como veremos existen algunas modificaciones extras que debemos realizar para retornar un listado con los clientes. Primero debemos definir una estructura que contenga a la definición planteada en el ejemplo anterior, y luego realizar una conversión un poco más compleja (de un vector de objetos, a un vector de vector). También voy a incluír una clase de soporte, que nos ayudará a realizar las conversiones.

Módulo Servidor

Para simplificar la estructura del servidor, intente extraer toda la lógica de conversión en una clase extra (smart_soporte.php), así la lectura del código del servidor se mantiene lo suficientemente limpia como para que sea más fácil su entendimiento. En el ejemplo anterior el servicio web retornaba un cliente al consumidor/cliente de dicho servicio web, en este caso, retornaremos un listado de clientes. Debemos tener en cuenta que las respuestas deben ser convertidas a los tipos de datos básicos, por lo tanto para devolver una lista de clientes, debemos convertir dicha lista en un lista de listas que contengan como indices/keys/claves los atributos de la clase Cliente.
A continuación expongo el código del servidor:
<?php
// Pull in the NuSOAP code
require_once('nusoap/lib/nusoap.php');
require_once('soporte_obrea.php'); // Incluyo este archivo que tiene archivos para soporte de WS

$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;



/*
 *  Ejemplo 3: listarClientes es la funcion mas compleja que voy realizar, no recibe parametros
 *  y retorna un listado de clientes. Utiliza varios metodos del ejemplo 2.
 */
 class Cliente {
  var $id;
  var $nombre;
 var $apellido;
 var $cuit;
}
class ClienteDAO {
 /**
  * getCliente($id) : Cliente
  * Esta funcion deberia implementarse con una conexion a una base de datos
  * y obtener la informacion de la base directamente
  */
 function getCliente($id) {
  $obj = new Cliente();
  if ($id==1) {
   $obj->id = 1;
   $obj->nombre = 'Blas';
   $obj->apellido = 'Pascal';
   $obj->cuit = '11-11111111-1';
  }
  if ($id==2) {
   $obj->id = 2;
   $obj->nombre = 'Isaac';
   $obj->apellido = 'Newton';
   $obj->cuit = '22-22222222-2';
  }  
  return $obj;
 }
 
 /**
  * getList : Array
  * Esta funcion retorna un listado de todos los clientes que estan en el sistema.
  * @return 
  */
 function getList() {
  $rta = array();
  $rta[0] = $this->getCliente(1);
  $rta[1] = $this->getCliente(2);
  return $rta;
 }
}

$server->wsdl->addComplexType('Cliente',
 'complexType',
 'struct',
 'all',
 '',
 array(
 'id' => array('name' => 'id', 'type' => 'xsd:int'),
 'nombre' => array('name' => 'nombre', 'type' => 'xsd:string'),
 'apellido' => array('name' => 'apellido', 'type' => 'xsd:string'),
 'cuit' => array('name' => 'CUIT', 'type' => 'xsd:string')
 )
);

$server->wsdl->addComplexType('listadoClientes',
 'complexType',
 'array',
 '',
 '',
 array (array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:Cliente[]'))
);
$server->register('listarClientes', // Nombre de la funcion
       array(), // Parametros de entrada
       array('return' => 'tns:listadoClientes'), // Parametros de salida
       $miURL
     );
function listarClientes() {
 $dao = new ClienteDAO();
 $listado = $dao->getList();
 $objSoporte = new SoporteWS();
 $respuesta = $objSoporte->convertirAVectorParaWS($listado);      
 return new soapval('return', 'tns:listadoClientes', $respuesta);
}


$server->service($HTTP_RAW_POST_DATA);
?>
Existen una lista de modificaciones que voy a ir detallando en los siguientes puntos.

Agregar una función en el DAO para que retorne una lista

Como mencione anteriormente en esta oportunidad debemos retornar un listado de clientes, por eso debemos modificar la clase ClienteDAO agregando un nuevo método getList, el cual retorna una lista con todos los clientes.
        function getList() {
  $rta = array();
  $rta[0] = $this->getCliente(1);
  $rta[1] = $this->getCliente(2);
  return $rta;
 }
La clase ObjetoDAO agrega un nuevo método llamado getList(), el cual retorna una lista de todos los clientes que tenemos en el sistema. Al igual que en el ejemplo 2, cuando implementemos este método en un servidor productivo, tendrá una lógica de acceso a datos para obtener la información de alguna fuente de datos. Por ejemplo, si la fuente de datos fuera una base de datos, deberiamos ejecutar una sentencia SQL similar a "select * from Cliente" y luego convertir los resultados en un listado de Clientes.

Definimos la estructura retornada

Debemos definir el nuevo tipo de datos que vamos a retornar, en este caso debemos retornar una lista de clientes, la cual esta definida por la siguiente estructura:
$server->wsdl->addComplexType('listadoClientes',
 'complexType',
 'array',
 '',
 '',
 array (array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:Cliente[]'))
);
Como podemos ver en el código anterior, definimos un tipo de datos "listadoClientes" el cual es un array de "Cliente" (el tipo de datos que habíamos definido en el ejemplo 2). Con estas simples lineas estamos indicando que vamos a retornar un listado (vector) y que cada elemento de dicho vector contendrá un Cliente.

Registramos el servicio web a publicar

$server->register('listarClientes', // Nombre de la funcion
       array(), // Parametros de entrada
       array('return' => 'tns:listadoClientes'), // Parametros de salida
       $miURL
     );
Al igual que en los ejemplos anteriores debemos registrar/publiar el servicio web, para ello llamamos a el método register del server. Definimos que el servicio web expuesto llamará a la función "listarClientes" y que la misma retornará un tipo de datos tns:listadoClientes (el tipo de datos definido en el punto anterior).

Implementación de la función

Debemos implementar la función que será llamada cuando el cliente del webservice haga uso del mismo. En este caso será implementado por la función listarClientes().
function listarClientes() {
 $dao = new ClienteDAO();
 $listado = $dao->getList();
 $objSoporte = new SoporteWS();
 $respuesta = $objSoporte->convertirAVectorParaWS($listado);      
 return new soapval('return', 'tns:listadoClientes', $respuesta);
}
Esta función es muy similar a la del ejemplo 2, pero en este caso hacemos uso de un objeto auxiliar (que he creado y el código del mismo esta disponible para descargar al final del artículo) el cual se encarga de la conversión del vector de objetos, en un vector de vector. La conversión es realizada por el método convertirAVectorParaWS del objeto $objSoporte (el cual es de la clase SoporteWS). Al igual que en los ejemplos anteriores, debemos convertir la respuesta en un tipo de datos predefinido, en este caso es el que creamos recientemente (tns:listadoClientes).
Como se puede ver, una vez pasado el ejemplo 1, los pasos son más repetitivos, hemos incorporado algunos objetos de soporte para mantener el código limpio, e intentado hacer el ejemplo cada vez un poco más cercano al mundo real. 

Módulo Cliente

El módulo cliente es el mismo que el del ejemplo 2, simplemente modificamos el nombre del script del servidor y el nombre de la función a llamar. En este caso el script del servidor tiene como nombre "nusoap_server_ej3.php" y la función que vamos a hacer uso en el webservice es "listarClientes"
<?php
require_once('nusoap/lib/nusoap.php');
// Crear un cliente apuntando al script del servidor (Creado con WSDL)
$serverURL = 'http://pruebas.orlandobrea.com.ar';
$serverScript = 'nusoap_server_ej3.php';
$metodoALlamar = 'listarClientes';

$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
 echo '<pre style="color: red">' . $error . '</pre>';
 echo '<p style="color:red;'>htmlspecialchars($cliente->getDebug(), ENT_QUOTES).'</p>';
 die();
}


// 2. Llamar a la funcion listadoClientes del servidor
$result = $cliente->call(
    $metodoALlamar,                     // Funcion a llamar
    array(),    // Parametros pasados a la funcion
    "uri:$serverURL/$serverScript",                   // namespace
    "uri:$serverURL/$serverScript/$metodoALlamar"       // SOAPAction
);
// Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
if ($cliente->fault) {
    echo '<b>Error: ';
    print_r($result);
    echo '</b>';
} else {
    $error = $cliente->getError();
    if ($error) {
        echo '<b style="color: red">Error: ' . $error . '</b>';
    } else {
     echo 'Respuesta: ';
  print_r($result);
    }
}
?>

Conclusión

Una vez que vamos avanzando en los ejemplos de WebServices podemos ver la complejidad de los mismos se puede mantener en un nivel similar (ayudandonos de algunos objetos de soporte). Como ya se habrán dado cuenta, no voy a describir la implementación de la clase SoporteWS ya que el código de la misma es código PHP standard, y escapa a los fines del ejemplo. Para simplificar lo que hace esta clase es convertir un objeto en un vector, y un listado de objetos, en un vector de vector. En el próximo ejemplo voy a hacer un ejemplo del mundo real, en el cual se listan los usuarios y al hacer click sobre un enlace, podemos ver todos los datos del mismo. El código de este ejemplo se puede descargar aquí.
Espero que los ejemplos hayan podido introducir a todas aquellas personas que estan realizando webservices, o estan planeando realizar webservices con PHP y NuSOAP.  

AGREGADO: Enlace para descargar el ejemplo completo

5 nov 2009

Regalo de Google - Crédito para probar AdWords

En el día de ayer recibí un e-mail de google que me ofrecía $190.- de crédito para comenzar a utilizar AdWords (seguramente muchos recibieron esta promoción). Al que no lo recibió le comento de donde se originó el e-mail y porque me llego.

Hace un tiempo (varios años que utilizo Google Analytics para uno de mis sitios web) y realmente nunca me termine de inscribir en AdWords, quizás porque generalmente los sitios que manejo tienen un buen posicionamiento (obviamente, cuando les presto atención). Pero, volviendo al tema, al estar inscripto en Google Analytics, y no en AdWords, Google lanzó una campaña de promoción de AdWords, en la cual dan un crédito de ARS $190.- para aquellos que se quieran iniciar en AdWords.

Para acceder a esto, simplemente hay que pegar el código que viene en el e-mail de Google, en el campo de código de promoción cuando uno inicia la cuenta de AdWords. Y ya tenemos nuestra cuenta de AdWords creada (el monto total del cupón de Google es de $205, pero como Google AdWords cobra un gasto por inicio de la cuenta, terminan quedando $190.-).

Es importante tener en cuenta que si nosotros asociamos nuestra tarjeta de crédito, automáticamente deberíamos disponer de dicho saldo para operar. En cambio, si seleccionamos como medio de pago Banelco / PagoMisCuentas se debe realizar un deposito de $50.- como mínimo para comenzar a utilizar la cuenta. Yo lo veo bien, y en caso que AdWords sea tan bueno como algunos dicen, es bueno invertir solo $50.- para tener de crédito $240.-

Es cuestión de arriesgarse, y ver que pasa. Tantas veces invertimos en gustos más caros para que terminen tirados por algún rincón de nuestra casa, porque no invertir $50.- con la posibilidad de generar más tráfico y ventas en nuestro sitio web.

Otro punto importante que es importante destacar para los usuario de Google AdWords de Argentina, es que ahora tiene mayor cantidad de medios de pago (antes mencione algo), por ejemplo, permite pagar con tarjeta de crédito (que no es ninguna novedad), pero también incorporó la posibilidad de hacer los pagos a través de la red Banelco de cajeros automáticos (ATM), y del sitio web de PagoMisCuentas.

Bueno, luego les comentare que tal me fué con esta prueba que estoy realizando, gracias al crédito de Google. A todos aquellos que le hayan llegado el cupón, les recomiendo que lo prueben, es una muy buena oportunidad para hacer algunas pruebas.

Buscar la página de Google AdWords de tu país

26 sept 2009

WebServices con NuSOAP en PHP - Ejemplo 2

En este segundo ejemplo de WebServices con PHP utilizando NuSOAP como biblioteca, voy a mostrar un ejemplo un poco más complejo que el del post anterior (WebServices con NuSOAP en PHP - Ejemplo 1). En este nuevo ejemplo voy a añadir un objeto "Cliente" en el servidor, el cual va a ser devuelto al consumidor/cliente del webservice (previa conversión del objeto a un vector). Para que quede más en claro, comenzare por los ejemplos del servidor y el cliente.

El servidor

Al ser un ejemplo un poco más complejo decidí agregar algunos comentarios más para que el código sea un poco más legible. Igualmente luego del código comentaré algunos fragmentos de código más en detalle.
 

<?php
// Importar la biblioteca NuSOAP
require_once('nusoap/lib/nusoap.php');
$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;

/*
* Ejemplo 2: getCliente es una funcion mas compleja que recibe un id y retorna el cliente
* que le corresponde (solo retorna 2 clientes). Tener en cuenta que las respuestas del
* WebService son un Array (vector) y no un objeto propiamente dicho. Si deseamos recuperar
* el objeto del lado del cliente, lo debemos convertir de vector a objeto. En el ejemplo 3
* se ampliara sobre este tipo de operaciones.
*/
// El objeto que voy a enviar al cliente como respuesta
class Cliente {
var $id;
var $nombre;
var $apellido;
var $cuit;
}
// Un objeto de acceso a datos (DAO - Data Access Object) que es el encargado de
// administrar los objetos almacenados (en este caso todos los valores estan
// prefijados, pero en un ejemplo real se podrian obtener de una base de datos)
class ClienteDAO {
/**
* getCliente($id) : Cliente
* Esta funcion deberia implementarse con una conexion a una base de datos
* y obtener la informacion de la base directamente
*/
function getCliente($id) {
$obj = new Cliente();
if ($id==1) {
$obj->id = 1;
$obj->nombre = 'Blas';
$obj->apellido = 'Pascal';
$obj->cuit = '11-11111111-1';
}
if ($id==2) {
$obj->id = 1;
$obj->nombre = 'Isaac';
$obj->apellido = 'Newton';
$obj->cuit = '22-22222222-2';
}
return $obj;
}
}
// Creo la estructura en la cual voya convertir al objeto Cliente, para enviarlo al
// consumidor del WebService (En este caso contiene un vector con id, nombre, apellido
// y cuit)
$server->wsdl->addComplexType('Cliente',
'complexType',
'struct',
'all',
'',
array(
'id' => array('name' => 'id', 'type' => 'xsd:int'),
'nombre' => array('name' => 'nombre', 'type' => 'xsd:string'),
'apellido' => array('name' => 'apellido', 'type' => 'xsd:string'),
'cuit' => array('name' => 'CUIT', 'type' => 'xsd:string')
)
);
// Registro la funcion que se expone en el webservice, la que puede consumir el cliente,
// e indico a que funcion PHP se debe llamar cuando el cliente invoque el webservice
// (getCliente)
$server->register('getCliente', // Nombre de la funcion
array('id' => 'xsd:int'), // Parametros de entrada
array('return' => 'tns:Cliente'), // Parametros de salida
$miURL
);
// Funcion que es llamada por el WebService de NuSOAP cuando el cliente intenta consumir
// el servicio web expuesto
function getCliente($id) {
$dao = new ClienteDAO();
$cliente = $dao->getCliente($id);
// Convierto el objeto en un vector, con una key por cada propiedad del objeto
$respuesta = array('id'=> $cliente-id,
'nombre' => $cliente->nombre,
'apellido' => $cliente->apellido,
'cuit' => $cliente->cuit
);
// Indico que el parametro de respuesta es del tipo tns:Cliente, y cual es el vector
// que contiene dicha estructura
return new soapval('return', 'tns:Cliente', $respuesta);
}

$server->service($HTTP_RAW_POST_DATA);
?>

En el códido de este nuevo ejemplo, existen algunas diferencias con respecto al del ejemplo1, por eso voy a intentar detallar cada una de ellas con un poco de detalle, para poder explicar de que se trata cada una de las nuevas incorporaciones.

Creamos un objeto Cliente

En este nuevo módulo servidor utilizamos una clase llamada "Cliente" que es la que luego de ser convertida, será devuelta al cliente/consumidor del WebService. Decidí crear una clase, para hacer el ejemplo un poco más acorde a lo que nos encontraremos en la vida cotidiana, es decir, hoy en día estamos muy acostumbrados a trabajar con objetos, y que toda la información que tengamos disponibles en nuestros sistemas se encuentren representadas por objetos. Es por eso, que comence  incorporar los objetos al ejemplo. La definición de la clase es la siguiente:

class Cliente {
var $id;
var $nombre;
var $apellido;
var $cuit;
}

Es un objeto muy simple, el cual no tiene asociaciones con otros objetos, simplemente tiene 4 atributos:

  • id
  • nombre
  • apellido
  • cuit

Si bien el objeto es muy simple, nos servirá para poder comenzar a ver cuales son las implicancias del mismo y como debemos convertirlo para luego retornarlo el cliente del webservice.

Creamos un objeto ClienteDAO

Siguiendo con la esta nueva meta de acercar el ejemplo a la vida real, incorporé un objeto DAO (Data Access Object). Para aquellos que no conocen o no están acostumbrados a usar un DAO, voy a intentar definirlo con unas pocas palabras. Cuando creamos aplicaciones multicapa, algo muy común hoy en día, es normal que una de las capas se llame DAO, que podríamos traducir como Capa de Acceso a Datos. Esta capa de acceso a datos, es la encargada de manejar los datos de la aplicación, por ejemplo la que se encarga de leer, guardar y borrar datos de un origen de datos, como podría ser una base de datos, un archivo XML, u otro webservice. Esta capa nos permite abstraer toda la lógica de acceso a datos, para que, si el día de mañana decidimos cambiar de motor de base de datos, o de origen de datos, no tengamos que modificar todas las demás partes del código, solamente cambiamos las clases de esta capa. Generalmente se utiliza un objeto DAO por cada objeto del dominio que queremos guardar/modificar/borrar (existen formas de agrupar en un único objeto las operaciones sobre varios objetos del dominio, pero es dependiente del lenguaje, y prefiero no ahondar en ello para no complicar el ejemplo).

Ya explicado, muy brevemente, que es un DAO vamos a la definición de la clase ClienteDAO:

class ClienteDAO {
/**
* getCliente($id) : Cliente
* Esta funcion deberia implementarse con una conexion a una base de datos
* y obtener la informacion de la base directamente
*/
public function getCliente($id) {
$obj = new Cliente();
if ($id==1) {
$obj->id = 1;
$obj->nombre = 'Blas';
$obj->apellido = 'Pascal';
$obj->cuit = '11-11111111-1';
}
if ($id==2) {
$obj->id = 1;
$obj->nombre = 'Isaac';
$obj->apellido = 'Newton';
$obj->cuit = '22-22222222-2';
}
return $obj;
}
}

Simplemente definimos un método, getCliente, para el objeto que crearemos. Para mantener el ejemplo sencillo, la información retornada por el mismo esta pre-fijada, es decir, no es tomada desde una base de datos, u otra fuente, como si sería en una aplicación del mundo real. Si este método lo implementaramos en una aplicación real, ejecutariamos una sentencia SQL similar a "select * from Cliente where id=".$id y luego convertiriamos la respuesta en un objeto Cliente que es el que retornamos.

Dado que es un ejemplo sin conexión con un origen de datos, esta simple función puede retornar solo 2 objetos clientes, uno para cuando se le pasa como parámetro 1, y otro para cuando se pasa como parámetro 2. En caso de pasar cualquier otro valor como parámetro la función retornará un objeto Cliente sin inicializar sus atributos.

Definimos el tipo de datos devuelto por el WebService

A diferencia del ejemplo 1, en el cual nuestro WebService solo retornaba un String (xsd:string), en esta oportunidad nuestro WebService, retornará un objeto (representado en un vector). Pero como estamos retornando un valor complejo, es decir, no es un string, o int, u otro tipo que ya este predefinido, debemos definirlo nosotros, para ello usamos el siguiente ceodigo:


En el código, estamos informandole a nuestro objeto $server, que utilizaremos un tipo de datos propio, o tipo complejo (este tipo de datos complejo se reflejará en nuestro WSDL del WebService). Los parámetros que más nos interesan para la función "addComplexType" son el primero, y el último. 

El primer parámetro define cual es el nombre que le asignamos a este tipo de datos complejo, en este caso "Cliente".

El último parámetro define como esta compuesto este tipo de datos complejo. Como podemos ver en el ejemplo, el tipo de datos se define como un array (vector) el cual esta compuesto por varios elementos (id, nombre, apellido, cuit), y cada uno de dichos elementos debe tener un vector que contenga "name" y "type" es decir la definición para cada tipo de datos que compone este tipo complejo. Lo que hacemos, no es más que definir un tipo de datos complejo, compuesto por diferentes tipos de datos simples (con simple, me refiero a aquellos que son los estandares) y/o otros tipos de datos complejos que ya hayan sido definidos. Es similar a la definición de una estructura de datos, u objeto. Debemos tener en cuenta que los tipos de datos simples tienen el prefijo xsd (recordarlo para incorporarlo a cada tipo que estamos definiendo).

Registración de la función a exponer

Al igual que en el ejemplo 1, debemos exponer cuales funciones deseamos publicar en nuestro WebService, para ello llamamos al método "register" de nuestro objeto $server, como podemos ver en el siguiente código:

$server->register('getCliente', // Nombre de la funcion
array('id' => 'xsd:int'), // Parametros de entrada
array('return' => 'tns:Cliente'), // Parametros de salida
$miURL
);

La registración de la función expuesta, es muy similar al ejemplo 1, la única diferencia es que en vez de retornar un tipo de datos xsd:string, retornamos un tipo de datos complejo tns:Cliente. Este tipo de datos, es el que definimos en el punto anterior. Con los últimos 2 puntos le estamos indicando a NuSOAP que vamos a retornar un tipo de datos complejo, el cual tiene la estructura definida en tns:Cliente (estructura definida en el punto anterior). 

Función que retorna el cliente

Al igual que en el ejemplo anterior tenemos una función que es la encargada de procesar el pedido y retornar la respuesta. En este caso dicha función se llama getCliente y es la siguiente:

function getCliente($id) {
$dao = new ClienteDAO();
$cliente = $dao->getCliente($id);
// Convierto el objeto en un vector, con una key por cada propiedad del objeto
$respuesta = array(
'id'=> $cliente->id,
'nombre' => $cliente->nombre,
'apellido' => $cliente->apellido,
'cuit' => $cliente->cuit
);
// Indico que el parametro de respuesta es del tipo tns:Cliente, y cual es el vector
// que contiene dicha estructura
return new soapval('return', 'tns:Cliente', $respuesta);
}

Al estar utilizando objetos para obtener los datos que deseamos retornar, tenemos algunos pasos más en al función que en el ejemplo anterior. 

  • Definimos en objeto $dato que es del tipo ClienteDAO (la capa de acceso a datos).
  • Obtenemos el cliente con el $id pasado como parámetro, a través del objeto DAO que creamos en el primer paso.
  • Convertimos el objeto cliente en un vector para luego poder retornarlo (usamos como key, el nombre de cada uno de los atributos del objeto).
  • Retornamos el vector, informando que el mismo es del tipo tns:Cliente (es decir, le pedimos a NuSOAP que nos cree un tns:Cliente a partir del vector pasado como parámetro.

¿Como creo el mapeo entre el objeto y el vector?

Para alcanzar este objetivo, cada uno puede tener sus preferencias, yo utilizó una correspondencia 1 a 1, entre el nombre del atributo y la key del vector. Ejemplo:

class Cliente { $vector = array(
var $id; --------> 'id' => '',
var $nombre; --------> 'nombre' => '',
var $cuit; --------> 'cuit' => ''
} );

Como vemos en el ejemplo, la propiedad id del objeto Cliente, se corresponde con la key 'id' del vector $vector. Cuando el objeto es copiado al vector, al acceder a $cliente->id; PHP no da el mismo valor que $vector['id'];

Cliente

El cliente no tiene mayores modificaciones que el del ejemplo 1, simplemente modifica el nombre del script del servidor, y la función a la cual se llama en el mismo.

<?php
require_once('nusoap/lib/nusoap.php');
// Crear un cliente apuntando al script del servidor (Creado con WSDL)
$serverURL = 'http://pruebas.orlandobrea.com.ar';
$serverScript = 'nusoap_server_ej2.php';
$metodoALlamar = 'getCliente';

$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
echo '<pre style="color: red">' . $error . '</pre>';
echo '<p style="color:red;'>htmlspecialchars($cliente->getDebug(), ENT_QUOTES).'</p>';
die();
}


// 2. Llamar a la funcion getCliente del servidor
$result = $cliente->call(
"$metodoALlamar", // Funcion a llamar
array('id' => '1'), // Parametros pasados a la funcion
"uri:$serverURL/$serverScript", // namespace
"uri:$serverURL/$serverScript/$metodoALlamar" // SOAPAction
);
// Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
if ($cliente->fault) {
echo '<b>Error: ';
print_r($result);
echo '</b>';
} else {
$error = $cliente->getError();
if ($error) {
echo '<b style="color: red">Error: ' . $error . '</b>';
} else {
echo 'Respuesta: ';
print_r($result);
}
}
?>

El cliente es muy sencillo, ya que simplemente muestra el contenido de la respuesta del servidor. En un ejemplo del mundo real, el vector que nos devuelve el servidor, lo deberíamos convertir a un objeto cliente (haciendo el mapeo inverso. Antes pasamos del objeto a un vector, y ahora deberíamos pasar del vector a un objeto). En el ejemplo 4, voy a hacer el cliente un poco más complejo, convirtiendo del vector a un objeto, y luego operando con el objeto.

Conclusión

En este ejemplo el servidor fué el que recibió modificaciones, para poder comenzar a operar con objetos, pero el cliente lo mantuve igual de simple que en el ejemplo anterior. También podés descargar el archivo comprimido con el ejemplo. Como vemos a medida que vamos aproximando nuestro ejemplo a la vida real, el mismo se hace un poco más complejo y debemos utilizar diferentes conocimientos que a veces escapan al del WebService en si. 

6 sept 2009

WebServices con NuSOAP en PHP - Ejemplo 1


Ya hace un tiempo realice un artículo sobre servicios web con PHP (utilizando NuSOAP), el mismo se puede acceder en WebServices con PHP usando NuSOAP, el mismo ya ha quedado un poco obsoleto debido a que el servidor que alojaba el webservice ya no lo contiene más. Por tal motivo y debido a que uno de los inconvenientes principales cuando uno se esta iniciando en el tema de los webservices con PHP, principalmente con NuSOAP, es encontrar como realizar el módulo servidor. En este primer post, decidí subir un ejemplo sencillo de comunicación entre un módulo servidor que provee expone una función, y un módulo cliente que las consume.

Introducción al ejemplo

No voy a exponer demasiados detalles con respecto a que son los WebServices, para ello pueden acceder al enlace que mencione anteriormente (WebServices con PHP usando NuSOAP). Solamente me limitare a explicar un poco el funcionamiento del mismo
Para el ejemplo, estoy utilizando la siguiente estructura de directorios


Siendo, nusoap_server_ej1.php el servidor de nustro WebService con NuSOAP, y nusoap_client_ej1.php el cliente.

Requerimientos

Los requerimientos para poner en funcionamiento el ejemplo son simplemente NuSOAP y tener algún servidor de sitios web (como puede ser Apache) corriendo PHP. Para todos aquellos que actualmente no tienen un servidor corriendo, pueden descargar XAMPP que es un servidor apache con PHP (también trae otras funcionalidades, pero estas 2 son las que importan para nuestro ejemplo) que solo se descarga, se descomprime, y con solo ejecutar un archivo, se inicia el servidor Apache con PHP en nuestra computadora, sin necesidad de mayores dolores de cabeza (Funciona tanto para Windows, como para OS-X).

Módulo servidor

El servidor de este ejemplo es muy sencillo, tan sencillo como el siguiente código:
<?php
 
// Incorporar la biblioteca nusoap al script, incluyendo nusoap.php (ver imagen de estructura de archivos)
require_once('nusoap/lib/nusoap.php');
// Modificar la siguiente linea con la direccion en la cual se aloja este script
$miURL = 'http://pruebas.orlandobrea.com.ar/nusoap_ej1';
$server = new soap_server();
$server->configureWSDL('ws_orlando', $miURL);
$server->wsdl->schemaTargetNamespace=$miURL;


/*
 * Ejemplo 1: getRespuesta es una funcion sencilla que recibe un parametro y retorna el mismo
 * con un string anexado
 */
$server->register('getRespuesta', // Nombre de la funcion
 array('parametro' => 'xsd:string'), // Parametros de entrada
 array('return' => 'xsd:string'), // Parametros de salida
 $miURL);
function getRespuesta($parametro){
 return new soapval('return', 'xsd:string', 'soy servidor y devuelvo: '.$parametro);
}

// Las siguientes 2 lineas las aporto Ariel Navarrete. Gracias Ariel
if ( !isset( $HTTP_RAW_POST_DATA ) )
    $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );

$server->service($HTTP_RAW_POST_DATA);
?>
El ejemplo es bastante sencillo, simplemente registra una función que recibe un parámetro y devuelve dicho parámetro con un texto adelante ("soy el servidor y devuelvo: ". El parámetro de entrada a la función es del tipo String, y la función también retorna un String.
Anteriormente mencione que no iba a detallar demasiado en código, ya que como pueden ver el mismo esta comentado, y creo que es bastante entendible.
Como podemos ver en el ejemplo, la complejidad mayor en crear el módulo servidor para un webservice, es crear la estructura de datos del mismo y sus tipos (ver $server->register() en el ejemplo). Si bien el ejemplo es muy sencillo, podemos ver que tanto los datos que el webservice recibe, como los que retorna, deben ser especificados en los tipos de datos predefinidos en XML para crear un Schema (xsd:string, xsd:int, etc.). En los próximos ejemplos que vaya subiendo de webservices con NuSOAP se podrá ver más en detalle algunos tipos de datos, y también como crear estructuras complejas (retornar objetos, o listas). Por el momento solo estamos recibiendo un String (xsd:string y retornamos otro entero (xsd:string).
En el ejemplo se puede ver que la función a la cual el webservice llama para atender la petición, en este caso getRespuesta, debe codificar la respuesta en el tipo de datos esperado. En este caso definimos que la función retornaría un xsd:string, por lo que debemos crear un soapval con el tipo de datos esperado.

¿Como se enlazan los datos recibidos por el servidor con nuestra función?

Cuando definimos los parámetros, tanto de entrada, como de respuesta, a los mismos le asignamos un nombre. Por ejemplo al parámetro de entrada le asignamos el nombre "parametro" (si, no es muy original, pero sirve para entender el ejemplo). Y el parámetro de salida, o retorno, lo llamamos "return". Entonces lo que hará NuSOAP es enlazar el parámetro de entrada a la función getRespuesta cuyo nombre es "parametro", con el valor que tome el "parametro" definido en el webservice. Voy a mostrár graficamente el enlace, para que sea más sencilla su visualización:
 

¿Que pasa si mi función NO recibe parámetros?

Simplemente modificariamos el código de:
$server->register('getRespuesta', // Nombre de la funcion
 array('parametro' => 'xsd:string'), // Parametros de entrada
 array('return' => 'xsd:string'), // Parametros de salida
 $miURL);
al siguiente código:
$server->register('getRespuesta', // Nombre de la funcion
 array(), // Parametros de entrada
 array('return' => 'xsd:string'), // Parametros de salida
 $miURL);
y obviamente a la función que hacemo referencia (getRespuesta) no debería recibir parámetros, por lo que pasaría de:
function getRespuesta($parametro){
a:
function getRespuesta(){

El cliente

Para este ejemplo que estoy mostrando, el cliente tiene un poco más de código que el servidor, para ejemplos un poco más complejos, el servidor comienza a ser un poco más largo, y el cliente mantiene apróximadamente la misma longitud. ¿Por que pasa esto? sencillo, a medida que el ejemplo se hace más complejo, se necesitán funciones más complejas en el servidor y principalmente, definir que los datos que vamos a utilizar también son más complejos. Pero eso lo dejaremos para futuros ejemplos que vaya subiendo al blog. Ahora nos concentraremos en el cliente, cuyo código es el siguiente:
<?php
// Incluimos la biblioteca de NuSOAP (la misma que hemos incluido en el servidor, ver la ruta que le especificamos)
require_once('nusoap/lib/nusoap.php');
// Crear un cliente apuntando al script del servidor (Creado con WSDL) - 
// Las proximas 3 lineas son de configuracion, y debemos asignarlas a nuestros parametros
$serverURL = 'http://pruebas.orlandobrea.com.ar';
$serverScript = 'nusoap_server_ej1.php';
$metodoALlamar = 'getRespuesta';

// Crear un cliente de NuSOAP para el WebService
$cliente = new nusoap_client("$serverURL/$serverScript?wsdl", 'wsdl');
// Se pudo conectar?
$error = $cliente->getError();
if ($error) {
 echo '<pre style="color: red">' . $error  . '</pre>';
 echo '<p style="color:red;'>htmlspecialchars($cliente->getDebug(), ENT_QUOTES).'</p>';
 die();
}

// 1. Llamar a la funcion getRespuesta del servidor
$result = $cliente->call(
 "$metodoALlamar", // Funcion a llamar
 array('parametro' => 'Orlando'), // Parametros pasados a la funcion
 "uri:$serverURL/$serverScript", // namespace
 "uri:$serverURL/$serverScript/$metodoALlamar" // SOAPAction
);
// Verificacion que los parametros estan ok, y si lo estan. mostrar rta.
if ($cliente->fault) {
 echo '<b>Error: ';
 print_r($result);
 echo '</b>';
} else {
 $error = $cliente->getError();
 if ($error) {
 echo '<b style="color: red">Error: ' . $error . '</b>';
 } else {
 echo 'Respuesta: '.$result;
 }
}

?>
Primero voy a hacer un breve resumen de cuales son los pasos que se realizan.
  • Incluír la biblioteca de NuSOAP de PHP.
  • Defino las variables de configuración (lo hice de esta forma para que les sea más sencillo el definir los parámetros para sus propios servicios web).
  • Creo el objeto NuSOAP Cliente (nusoap_client).
  • Verifico que la conexión se haya podido realizar.
  • Llamó al servicio web del servidor (función getRespuesta definida en las variables de configuración).
  • Verifico que la llamada haya sido exitosa, en caso de ser exitosa muestro la respuesta del servidor, y en caso de error, muestro el mensaje de error.
El código del cliente también esta comentado, y definí 3 variables al principio que nos permitirán adaptar el cliente del WebService a nuestras pruebas particulares.
En el cliente no hago un procesamiento de la información recibida desde el servidor de WebServices (NuSOAP server) sino que la muestro directamente por pantalla, cuando estemos trabajando realmente con dicha información debemos realizar un procesamiento de la información recibida antes de utilizarla (el procesamiento puede ser desde algo tan sencillo como mostrar la información en un sitio especifico del website, hasta generar tablas o campos de edición). Las posibilidades de procesamiento son infinitas, y dependen de para que estemos utilizando el WebService, y la información otorgada por el mismo.

Verificación de los scripts

Para comprobar que nuestros scripts estan funcionando, lo que primero recomiendo es primero acceder al script del servidor (http://<direccion de nuestro server>/nusoap_server_ej1.php) desde nuestro navegador, el cual nos debe mostrar una pantalla similar a la siguiente si todo esta correcto (en caso contrario mostrará un típico error PHP por pantalla):

Y si hacemos click sobre el nombre la la función que hemos publicado en nuestro servidor de WebService, nos mostrará algo similar a esto:

Cuando hemos verificado que el servidor esta funcionando, ahora realizaremos la prueba del cliente (http://<direccion de nuestro server>/nusoap_client_ej1.php), y nos mostrará una página similar a la siguiente:

Conclusión

El WebService aquí presentado es un ejemplo básico para todas aquellas personas que estan comenzando con ellos y desean utilizar NuSOAP para crear los mismos y consumirlos.  El código del ejemplo es muy sencillo, pero igualmente decidí publicar un archivo con el ejemplo del cilente y el servidor, para que sea más sencilla la implementación del ejemplo en sus computadoras (Descargar el ejemplo).