Salesforce Communities – Gestión automática de licencias Customer Community

Hace ya algunos meses que no escribía un post, pero he sacado algo de tiempo y me he puesto manos a la obra.

Este post lo dedico a las Comunidades de Salesforce, y más en concreto a la gestión de licencias Customer Community.

Uno de los últimos trabajos en los que ha participado ticMind ha sido un proyecto muy ambicioso para una universidad española, con un portal de Comunidades al que accederán miles de usuarios, integrado con el LMS XCHOOL y en el que se ha desarrollado un sistema de gamificación custom.

Integración con LMS XCHOOL

Integración con LMS XCHOOL

Debido al elevado número de usuarios que accederán al portal de Comunidades, surgió la necesidad de desarrollar una gestión inteligente de licencias. Una parte de las licencias utilizadas son del tipo Customer Community, es decir licencias basadas en usuario, mientras que otra gran parte son del tipo Customer Community Login, es decir, licencias basadas en login. Debido a la diferencia en el precio de ambos tipos de licencias, se trataba de desarrollar una solución que realizase una gestión automática de licencias, asignando las licencias de tipo Customer Community a los usuarios que accediesen más veces a la Comunidad y asignando las de tipo Customer Community Login a los usuarios que menos accediesen.

Sistema de gamificación

Sistema de gamificación

Antes de comenzar con el análisis de la solución, buscamos productos en el AppExchange que cubrieran los requisitos mínimos, pero lo único que encontramos fue una aplicación en la que la reasignación de licencias se realizaba de forma manual, con lo que quedó descartada.

Esta gestión automática de las licencias la hicimos también extensible a la página de autoregistro de la Comunidad, en la que los usuarios se pueden dar de alta. En función de la disponibilidad de licencias de ambos tipos, se asigna al usuario el tipo de licencia correspondiente. Si no quedasen licencias disponibles, se le indica al usuario que quiere darse de alta en la Comunidad y se notifica al administrador del sistema que todas las licencias están asignadas. En este proceso, la gestión de licencias se realiza de forma síncrona.

Para la optimización y reasignación automática de licencias, cada noche se lanza un proceso batch que es el encargado de realizar esta labor. Este proceso realiza el cálculo del número de logins de cada usuario de Comunidades dentro del intervalo de tiempo configurado. En función de este número de logins obtenido se realiza una reasignación de licencias, de modo que las licencias del tipo Customer Community siempre se asignen a los usuarios que más acceden a la Comunidad, optimizando de esta forma los costes para el cliente.

Se ha optado por realizar esta gestión de licencias como proceso batch debido al alto número de registros que debemos procesar. Además, de esta forma no habría ningún problema si el número de usuarios se incrementase en un futuro.

Durante el desarrollo de la solución, como era de esperar, surgieron algunas dificultades. Algunas fueron solventadas de una forma sencilla, pero otras fueron más complejas y más costosa su resolución. Una muestra de las barreras que surgieron queda representada por la utilización en la solución final de 4 procesos batch relacionales (cada proceso batch se lanza una vez finalizado el proceso batch anterior). Gracias procesos batch!!!

Gracias a los retos de este tipo, como desarrollador uno se siente vivo, y la satisfacción final que se consigue no tiene precio.

Hasta el próximo post.

 

Anuncios

Salesforce III Developer Day en Madrid el Miércoles 22 de Octubre de 2014

III Developer day

El próximo día 22 de Octubre se celebra en el Centro de Innovación del BBVA el evento Salesforce III Developer Day Madrid. La dirección es: Plaza de Santa Bárbara, 2, Madrid.

La Agenda prevista para este evento es la siguiente:

9:30 – 10:00 Registro
10:00 – 10:30 Introducción a Salesforce y la plataforma Salesforce1
10:30 – 11:30 Crea una App y su base de datos
11:30 – 12:00 Customiza la interfaz de usuario
12:00 – 12:30 Descanso y coffee break
12:30 – 13:15 Lógicas de negocio con Clicks y no con código
13:15 – 13:45 Moviliza tu App con Salesforce1
13:45 – 14:00 App Exchange
14:00 – 14:20 Dudas y preguntas
14:20 – 15:20 Coctel y networking

En el evento intervendrán Eduardo Sánchez (Salesforce.com) y José Luis Almazán (ticMind Consulting).

El evento principalmente va enfocado a desarrolladores que quieran tener un primer contacto con Salesforce.com y la plataforma Salesforce1. Es muy recomendable asistir con el portátil.

Inscripción

La inscripción al evento es gratuita, con un número de plazas limitado.

Para inscribirte en el evento Salesforce III Developer Day puedes hacerlo desde aquí.

Links de interés

A continuación os indicamos los links para que podáis descargaros los documentos que vamos a utilizar en la presentación:

Desde aquí queremos agradecer la confianza depositada por Salesforce.com en el equipo de ticMind y darnos de nuevo la oportunidad de participar en un evento de este tipo.

Os esperamos!

Entendiendo Salesforce communities

En el pasado Dreamforce, Salesforce sacaba a la luz Communities, durante mi visita a Dreamforce del año pasado, tuve la suerte de reunirme con los Product Managers para conocer de primera mano este producto. Durante unos meses hemos participado en el piloto interno de la herramienta aprendiendo las nuevas posibilidades que este nuevo concepto de colaboración nos podía ofrecer y realizando diferentes demos para testear la forma en la que podía sernos útil este nuevo tipo de licenciamiento, como conclusión, podemos decir que Communities mejora notablemente nuestros antiguos portales de cliente y partners, añadiéndoles la posibilidad de ser customizados por completo de una manera sencilla o de una manera más profunda ya sea usando site.com o desarrollando por completo nuestras páginas con apex, además nos da la posibilidad de agregar a nuestros portales funcionalidades hasta ahora imposibles con las anteriores licencias como por ejemplo:

Chatter
Chatter answers
Ideas
Salesforce Knowledge
Autenticación delegada
Customizaciones de Url, Emails, Página de login etc.

Cabe destacar que todo esto se puede usar  de una forma segura y segmentada, de esta forma Communities nos permite crear grupos acotados de usuarios que colaboren entre sí ya sean clientes, partners o empleados internos de nuestra organización. Entre las muchas utilidades que se le puede dar a Communities, destacan por ejemplo las comunidades de atención al cliente, podemos tener una comunidad donde nuestros clientes se ayuden entre sí, abaratando nuestro servicio de atención al cliente y mejorándolo considerablemente, como ejemplo os muestro una de las comunidades que hemos construido a modo de demostración: En primer lugar,  como podemos ver en la siguiente imagen, el look and feel de la página de login es totalmente custom y al estar trabajando sobre un site, nada nos impediría mostrar datos dinámicos como noticias o información que saliera de nuestros objetos de Salesforce permitiéndonos crear una home tan completa y dinámica como queramos.

Comlogin

Al acceder a nuestra comunidad, se accede a una pantalla donde se pueden ver las soluciones dadas de alta en nuestra organización en forma de árbol de navegación, de esta forma modificar los contenidos de esta página se hará de manera sencilla y dinámica, además al tener los datos del cliente que está accediendo en nuestra comunidad, en función de los productos que tengan contratados con nosotros, verán una información personalizada, dando un mejor servicio, mejorando la experiencia de usuario y mejorando la efectividad de los mensajes comerciales que se les quiera mostrar. Según el usuario navega por el árbol de soluciones, obtendrá ayuda en modo de vídeos y artículos, los comentarlos y valoraciones, así como todo el registro de navegación de los usuarios, queda almacenado en nuestra organización, dándonos una idea de la utilidad de nuestras soluciones y el interés por nuestros productos. Esta comunidad está construida sobre licencias “Customer Communities” con lo que hemos podido dotarle de la potencia de Service Cloud y de esta forma podemos redirigir los casos al equipo que mejor preparado esté para resolverlos de manera automática en función del lugar desde donde se crearon, también hemos integrado el Live Agent de Salesforce e implantado la Service Cloud Console para la gestión interna de incidencias por parte de los agentes.

FAQ

Al tener disponible Chatter en este nuevo tipo de licenciamiento, hemos creado una pantalla desde la cual pueden acceder a los grupos de Chatter a los que pertenecen, como se puede ver en las imágenes, el selector de grupos también ha sido customizado mostrando únicamente la información necesitemos:

ComChatter

ComChatterGroup

Por último os mostramos el uso de Chatter Answers desde nuestro portal, de esta forma los usuarios pueden realizar preguntas a la comunidad y responderse las dudas entre ellos,  así como consultar dudas anteriormente resueltas a otros usuarios.

ComPreguntas

Como podéis ver, una comunidad nos permite dar a nuestros clientes y socios unos servicios que hasta hace poco nos habría costado meses construir de una manera sencilla y segura, este es el caso que os he presentado, pero con Communities también podemos hacer partícipes a nuestros usuarios de nuestros procesos de negocio dándoles no solo funcionalidad añadida sino enriqueciendo nuestra base de datos y nuestros procesos con su participación en los mismos.

Por último, os comparto este video de demostración de Salesforce sobre communities, donde nos podemos hacer una idea más general de las funcionalidades que Communities nos brinda. No dudéis en poneros en contacto con nosotros ante cualquier duda/pregunta.

Salesforce Barcelona continúa imparable

Lo que parecía el hermano pequeño de la edición de Essentials Madrid, empieza a coger más renombre en la ciudad condal. Ayer Barcelona fue testigo del gran potencial de la herramienta de gestión CRM Nº1 en el mundo, Salesforce continúa imparable. En total, se acercaron al auditorio más de 800 personas, las cuales pudieron disfrutar de una jornada llena de presentaciones, experiencias y muchas caras conocidas del sector tecnológico.

Y es que no es de extrañar que cada vez sean más las empresas que se quieran sumar a la ola del éxito de la implantación de Salesforce como CRM.

“Salesforce es algo más que un solo producto – es una solución completa para la gestión integrada de todas las interacciones con sus clientes actuales y potenciales, diseñados para ayudar a su organización a crecer y tener éxito”

Su éxito y sus miles de implantaciones dan fe del triunfo de la herramienta de gestión de cliente.

En la Keynote el equipo directivo de Salesforce con Enrique Polo a la cabeza nos contaron que a finales de este año se prevé que Salesforce sea la 4ª empresa de software a nivel mundial, o como la 4ª revolución industrial ya es una realidad, una etapa a la que Salesforce denomina “la era del Cliente” porque se basa en la redefinición de los procesos para centrarlos en el cliente, y para comunicarlo con otras personas (ya sean clientes, socios, compañeros de trabajo..) y con los objetos que nos rodean. En este contexto, el Smartphone mediante sus Apps cobra especial importancia como medio preferido para estas interacciones.

Se enseñaron las últimas novedades de sus productos Sales, Service, Communities, Plataforma, Marketing y Wave, centradas sobre todo en la incorporación de Lightning. Ahora, los clientes, los desarrolladores y los socios pueden trabajar y crear componentes personalizados de una manera mucho más sencilla, mejorando así la experiencia de usuario, gracias a Lightning.

Caso de éxito bq

Entre los invitados al evento se encontraba la compañía española bq. Rodrigo del Prado, Consejero Delegado Adjunto, subió al escenario para contar el caso de éxito del que TICMIND ha sido partícipe como PARTNER de la gran marca española.

BQ presentando

Más tarde tuvimos la oportunidad de charlar un rato y sacarnos una foto juntos.

BQ rodrigo

Sesiones para todos

Además, tuvieron lugar multitud de sesiones sectoriales, donde se pudo comprobar como las soluciones de Salesforce se adaptan a los procesos de todo tipo de empresas y sectores: manufactura, distribución y gran consumo, turismo, transporte, seguros. Fueron algunas de las ponencias a las que los invitados pudieron asistir.

Ayer fueron muchos los clientes que se nos acercaron para conocer cuál es la solución y el modelo que mejor se adapta a su negocio. Nosotros, una vez más, les asesoramos ofreciéndoles una consultoría y gestión cuidada y personalizada hasta el más mínimo detalle.

Stand

En Ticmind tenemos una obsesión “lograr un 10 de satisfacción de nuestros clientes” y lo conseguimos. ¿Quieres saber cómo?

¡Esperamos volver a veros pronto!

 

Salesforce Essentials Madrid 2016

Madrid 8 de la mañana, amanece un precioso miércoles de mayo y todo está listo, a falta de unos pequeños detalles, para recibir a los cientos, que luego se convertirían en miles de asistentes.

La galardonada como empresa más innovadora del mundo durante varios años consecutivos, celebra cada año su particular Mini Dreamforce en numerosas ciudades del mundo, entre las que destacan en nuestro país las ediciones de Madrid y Barcelona.

Miles de personas se reúnen en torno a la nube listas para disfrutar de los últimos avances e innovaciones que la compañía presenta. Un evento cargado de emoción y espectáculo que no deja indiferentes ni a Partners ni a clientes.

006_27227550166_o

Este año no iba a ser menos. El líder del sector en el plano tecnológico del CRM, presentó los proyectos más innovadores del mercado español, entre los que se encontraban el exitoso proyecto de la compañía BQ y el de La Universidad de Alcalá de Henares, primera universidad española en tener un portal de emprendimiento ad hoc en la nube.

 

Caso de éxito BQ

La compañía de telefonía española BQ atraviesa desde hace varios años uno de sus mejores momentos en lo que ha crecimiento empresarial respecta. Esta situación obligó a la firma a mejorar y controlar más sus procesos internos.

Ticmind se puso manos a la obra y desarrolló y adaptó en tiempo récord su mejor solución.

Rodrigo del Prado subía al escenario a primera hora de la mañana para hablar de lo que Salesforce ha calificado ya como un Caso de Éxito.

066_27164012862_o

 

La Universidad de Alcalá de Henares y el Premio al mejor programa extranjero sobre emprendimiento

La jornada transcurría de manera amena entre las salas de IFEMA, los numerosos stands de los Partners, visitas de clientes, felicitaciones y viandas, cuando llegó el turno de presentar el Proyecto de Emprendimiento de la UAH.

El galardón, que recae por primera vez en una entidad europea, reconoce la “introducción del espíritu emprendedor en los planes de estudios o el desarrollo de programas excepcionales”. Llegó el 10 de enero, día en el que se decidía el ganador del premio, y finalmente la universidad quedó como finalista. Un hecho que no hizo empañar su enorme éxito en demostrar que hay mucho recorrido pendiente en el ámbito de la tecnología y la educación.

Salvador Méndez fue el encargado de presentar el internacionalmente reconocido proyecto.

IMG_20160525_171153

Un año más se reunían en IFEMA los mejores Partners y clientes de la mano de Salesforce, cerrando la edición con un récord de asistentes, más de 2.200.

El cocinero de tres Estrellas Michelín Dabiz Muñoz, fue el encargado de cerrar la jornada explicando su modelo de negocio y dejando a todos los asistentes muy buen sabor de boca.

IMG_20160525_180412

¡Nos vemos el día 9 de Junio en Essentials Barcelona!

 

Código Apex y Visualforce de FindNearby

Éste es el código que usaremos para el ejemplo de mostrar las cuentas cercanas a nosotros y que estén a menos de 1 Km de distancia.

Clase Apex FindNearby:

global with sharing class FindNearby {
    public FindNearby(ApexPages.StandardController controller) {}

    @RemoteAction
    // Find Accounts nearest a geolocation
    global static List<Account> getNearby(String lat, String lon) {
        // If geolocation is not set, use Plaza Santa Bárbara, 2, Madrid
        if(lat == null || lon == null || lat.equals("") || lon.equals("")) {
            lat = "40.427305";
            lon = "-3.696754";
        }

        // SOQL query to get the nearest warehouses
        String queryString =
            "SELECT Id, Name, Location__Longitude__s, Location__Latitude__s " +
            "FROM Account " +
            "WHERE DISTANCE(Location__c, GEOLOCATION("+lat+","+lon+"), \"km\") < 1 " +
            "ORDER BY DISTANCE(Location__c, GEOLOCATION("+lat+","+lon+"), \"km\") " +
            "LIMIT 10";

        // Run and return the query results
        return(database.Query(queryString));
    }
}

Página Visualforce FindNearbyAccountPage:

<apex:page sidebar="false" showheader="false" standardController="Account" extensions="FindNearby">
    <!-- Include in Google Maps API via JavaScript static resource -->
    <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>

    <!-- Setup the map to take up the whole window -->
    <style>
        html, body { height: 100%; }
        .page-map, .ui-content, #map-canvas { width: 100%; height:100%; padding: 0; }
        #map-canvas { height: min-height: 100%; }
    </style>

    <script>
        function initialize() {
            var lat, lon;

            // If we can, get the position of the user via device geolocation
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(function(position){
                    lat = position.coords.latitude;
                    lon = position.coords.longitude;
                    console.log(lat);
                    console.log(lon);

                    // Use Visualforce JavaScript Remoting to query for nearby accounts
                    Visualforce.remoting.Manager.invokeAction("{!$RemoteAction.FindNearby.getNearby}", lat, lon,
                        function(result, event){
                            if (event.status) {
                                console.log(result);
                                createMap(lat, lon, result);
                            } else if (event.type === "exception") {
                                //exception case code
                            } else {
                            }
                        },
                        {escape: true}
                    );
                });
            } else {
                // Set default values for map if the device does not have geolocation capabilities
                /** Plaza Santa Bárbara, 2, Madrid **/
                lat = 40.427305;
                lon = -3.696754;

                var result = [];
                createMap(lat, lon, result);
            }
        }

        function createMap(lat, lon, accounts){
            // Get the map div, and center the map at the proper geolocation
            var currentPosition = new google.maps.LatLng(lat,lon);
            var mapDiv = document.getElementById("map-canvas");
            var map = new google.maps.Map(mapDiv, {
                center: currentPosition,
                zoom: 14,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            });

            // Set a marker for the current location
            var positionMarker = new google.maps.Marker({
                map: map,
                position: currentPosition,
                icon: "https://maps.google.com/mapfiles/ms/micons/green.png"
            });

            // Keep track of the map boundary that holds all markers
            var mapBoundary = new google.maps.LatLngBounds();
            mapBoundary.extend(currentPosition);

            // Set markers on the map from the @RemoteAction results
            var acc;
            for(var i=0; i<accounts.length;i++){
                acc = accounts[i];
                console.log(acc);
                setupMarker(acc, map, mapBoundary);
            }

            // Resize map to neatly fit all of the markers
            map.fitBounds(mapBoundary);
        }

        function setupMarker(acc, map, mapBoundary){
            var accountNavUrl;

            // Determine if we are in Salesforce1 and set navigation link appropriately
            try{
                if(sforce.one){
                    accountNavUrl =
                        "javascript:sforce.one.navigateToSObject(\"" + acc.Id + "\")";
                }
            } catch(err) {
                console.log(err);
                accountNavUrl = "\\" + acc.Id;
            }

            var accountDetails =
                "<a href=\"" + accountNavUrl + "\">" + acc.Name + "</a>";

            // Create the callout that will pop up on the marker
            var infowindow = new google.maps.InfoWindow({
                content: accountDetails
            });

            // Place the marker on the map
            var marker = new google.maps.Marker({
                map: map,
                position: new google.maps.LatLng(
                                acc.Location__Latitude__s,
                                acc.Location__Longitude__s)
            });
            mapBoundary.extend(marker.getPosition());

            // Add the action to open up the panel when it is marker is clicked
            google.maps.event.addListener(marker, "click", function(){
                infowindow.open(map, marker);
            });
        }

        // Fire the initialize function when the window loads
        google.maps.event.addDomListener(window, "load", initialize);
    </script>

    <!--  All content is rendered by the Google Maps code -->
    <!--  This minimal HTML justs provide a target for GMaps to write to -->
    <body style="font-family: Arial; border: 0 none;">
        <div id="map-canvas"></div>
    </body>
</apex:page>

Salesforce Essentials Madrid y DeveloperZone el Jueves 06 de Marzo de 2014

Salesforce Essentials y DeveloperZone Madrid Como ya sabéis, el próximo día 6 de Marzo se celebra en el Colegio de Arquitectos de Madrid el evento Salesforce Essentials Madrid.

La dirección es: Calle Hortaleza 63. Disponibilidad de parking (plazas limitadas) y servicio de aparcacoches. Metro Alonso Martínez.

En Salesforce Essentials Madrid encontrará:

  • Demostraciones en directo de la plataforma la plataforma Salesforce1, Sales Cloud, Service Cloud y Marketing Cloud.
  • Referencias de clientes de diversos sectores y países compartiendo cómo Salesforce está impulsando la transformación de sus empresas.
  • Expo Room con la mejor oferta de nuestra comunidad de partners.

La Agenda prevista para este evento es la siguiente:

9:00 – 10:00 Registro y Café de Bienvenida
10:00 – 11:30 Keynote. Internet of Customers. Salesforce1, Marketing Cloud, Sales Cloud, Service Cloud
Miguel Milano Aspe. Presidente de Salesforce EMEA
Enrique Polo de Lara. Director General de Salesforce España y VP regional
Caso de Cliente: Telefónica
Caso de Cliente: Groupalia
11:30 – 12:00 Coffee Break (Zona Expo)
12:00 – 13:00 Sala de Keynotes: Casos de Exito. Clientes y Partners Salesforce
13:00 – 14:15 Almuerzo Cóctel (Zona Expo)
14:15 – 15:00 Sala de Keynotes: Track de Marketing Cloud, Caso de cliente: Sony
15:00 – 15:30 Resumen y despedida
15:30 – 17:00 Cóctel de cierre

En paralelo a este evento, se celebra en el mismo lugar la DeveloperZone. Esta sesión tendrá un carácter técnico y la agenda prevista será la siguiente:

9:00 – 12:00 Lo mismo que para Salesforce Essentials
12:00 – 14:00 Track de Desarrolladores. Durante este tiempo se enseñará a los asistentes cómo crear una aplicación en la plataforma de desarrollo móvil Salesforce1.
14:00 – 18:00 Durante la sesión de tarde se desarrollarán de forma paralela dos actividades:

  • Concurso de Desarrollo. Durante este tiempo los desarrolladores podrán crear su aplicación en Salesforce1, con acceso a expertos para ayudas, consejos y resolución de dudas.
  • De forma paralela al concurso se habilitará una sala donde los gurús impartirán breves píldoras de formación, tales como: Salesforce Communities, Salesforce Sites, Mecanismos de Integración…

* Durante toda la sesión habrá un servicio de catering para los Desarrolladores.

Inscripción

La inscripción en ambos eventos es gratuita, con un número de plazas limitadas.

Para inscribirte en el evento Salesforce Essentials Madrid, puedes hacerlo desde aquí.

Debido a que la DeveloperZone lleva un registro diferente al evento de Salesforce Essentials Madrid, todos aquellos interesados en asistir al DeveloperZone, debéis de escribir un email a Ignacio Saenz (isaenzabenza@salesforce.com) con vuestros datos, nombre, email y empresa.

Esperamos veros por allí.

JavaScript Remoting de Salesforce más allá de Visualforce y Apex. Proyectos Web en Force.com totalmente dinámicos.

JavaScript Remoting for Apex ControllersEn este post vamos a intentar explicar lo mejor posible qué es JavaScript Remoting, ventajas e inconvenientes de su utilización y alguna funcionalidad no documentada que hemos encontrado analizando el interior de la librería JavaScript que utiliza Salesforce en las páginas Visualforce.

¿Qué es JavaScript Remoting?

JavaScript Remoting es una funcionalidad de la plataforma Force.com de Salesforce que nos permite invocar métodos en controladores APEX desde JavaScript en nuestras páginas Visualforce. Dicho de otra forma, es una funcionalidad con la que podemos invocar métodos del servidor (APEX) desde la página Web cliente (página Visualforce) mediante peticiones AJAX.

Gracias a JavaScript Remoting podemos crear páginas con comportamiento complejo, dinámico y totalmente interactivas que no es posible con los componentes AJAX estándar de Visualforce.

Es importante tener en cuenta que la petición al controlador APEX se realiza de forma síncrona, pero la respuesta del servidor hacia la página Visualforce es asíncrona. Comprender esto es de vital importancia para  realizar un buen uso de esta funcionalidad.

La funcionalidad de JavaScript Remoting se puede dividir en 3 partes:

  • La invocación de los métodos remotos que se añaden a la página Visualforce, escrito en JavaScript.
  • La definición de los métodos remotos en la clase del controlador APEX. Esta definición de los métodos se escribe en código APEX.
  • La función JavaScript que se encargará de obtener y procesar la respuesta del controlador, escrita en JavaScript.

¿Para qué sirve JavaScript Remoting?

Fundamentalmente lo que conseguimos con JavaScript Remoting es poder recargar ciertas partes de una página Visualforce sin tener que recargar la página completa. De esta forma podemos crear páginas Visualforce muy complejas, totalmente dinámicas, con cualquier tipo de funcionalidad que requiera tener siempre los datos actualizados en la página Visualforce, y con una mínima transferencia de datos entre cliente y servidor, y todo esto sobre la plataforma Force.com.

Un buen ejemplo del uso de JavaScript Remoting es el Servicio de Empleo de élogos (Agencia Privada de colocación). Este vídeo producido por Salesforce da una visión general de él:

Para poder comprender la potencia de JavaScript Remoting, indicaros que para este proyecto utilizamos en su día una sola página Visualforce, dentro de la cual nos encontramos con numerosos componentes Visualforce customizables y entre las muchas funcionalidades que tiene, podemos encontrar Correo interno, Preguntas y respuestas, Calendario de eventos, Notificaciones, Gamificación, Chat, Videoconferencia, Agenda de contactos, Visor de documentos… y un largo etc… Toda la información se muestra en tiempo real, y con velocidades de carga y ejecución muy altas. A continuación os mostramos en imágenes algunas de estas funcionalidades:

El pase de diapositivas requiere JavaScript.

¿Cuáles son las ventajas de utilizar JavaScript Remoting?

Podemos destacar las siguientes ventajas:

  • Nos permite realizar numerosas actualizaciones sobre la página sin tener que recargar la página completa.
  • Ofrece mucha flexibilidad con lo que podemos crear comportamientos complejos y dinámicos.
  • El estado de la vista (View State) no se pasa al servidor, con lo que la velocidad de proceso es mucho mayor. Únicamente se pasan al servidor los parámetros que nosotros queramos. De igual forma, la respuesta del servidor sólo llevará los datos que nosotros necesitemos.
  • El código se ejecuta en el contexto del usuario que visualiza la página.
  • Se desvincula la página del controlador.
  • Los métodos admiten como argumentos tipos primitivos APEX, colecciones, sObjects genéricos y tipados, y clases e interfaces APEX definidas por el usuario.

¿Cuáles son los inconvenientes de utilizar JavaScript Remoting?

Podemos destacar las siguientes ventajas:

  • Al ser la respuesta del servidor asíncrona, se pueden producir conflictos o comportamientos inesperados, aunque en muy raras ocasiones.
  • Se requieren conocimientos de JavaScript.
  • Como no se pasa el estado de la vista al servidor, deberemos pasar por parámetros todos aquellos valores que ya tengamos en la página y que necesitemos en los métodos del controlador, para no volver a obtenerlos en el servidor.
  • La respuesta del servidor está limitada a un máximo de 15 Mb (en mi caso nunca he llegado a ese límite).
  • Debemos tener en cuenta que Salesforce lo considera como llamadas API, por lo que cuentan para el límite de llamadas API  de nuestra organización de Salesforce en un período de 24 horas. Vota esta idea para que Salesforce no las tenga en cuenta para el límite de llamadas API. Gracias a la valiosa ayuda de Carolina Ruiz, Salesforce ha confirmado que las llamadas de los métodos JavaScript Remoting no cuentan para el límite de llamadas API, lo cual es una gran noticia para todas aquellas organizaciones con límites muy bajos.

Sintaxis de JavaScript Remoting

Para poder utilizar JavaScript Remoting en nuestras páginas Visualforce, debemos agregar el siguiente código:

[namespace.]controller.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);

Donde:

  • namespace: es el “espacio de nombres” de la clase del controlador. Se requiere si tu organización tiene definido un espacio de nombres o si la clase proviene de un paquete instalado.
  • controller: es el nombre de tu controlado APEX.
  • method: es el nombre del método APEX que queremos invocar.
  • parameters: es el listado de parámetros, separados por comas, que tu método APEX espera recibir.
  • callbackFunction: es el nombre de la función JavaScript que manejará la respuesta del controlador y recibirá el estado de la llamada al método (event) y el resultado del mismo (result), como parámetros:
    • result: es el objeto que contiene la respuesta del método del controlador APEX.
    • event: es el objeto que contiene el estado de la llamada. Tiene como campos:
      • status: si todo ha ido bien, si valor es true, en caso contrario false, lo cual indica que se ha producido algún error.
      • type: indica el tipo de respuesta; rpc para una respuesta satisfactoria, exception si se ha lanzado alguna excepción en el método remoto, etc…
      • message: si se ha producido algún error, aquí se devuelve el mensaje de error correspondiente.
      • where: contiene el seguimiento de pila APEX, si se generó por el método remoto.
  • configuration: se utiliza para modificar el comportamiento de la llamada remota. Entre los parámetros que podemos configurar, tenemos:
    • buffer (Boolean): si las invocaciones a los métodos remotos se ejecutan muy seguidas en el tiempo, Salesforce las agrupa en una sola petición y las ejecuta en el servidor una detrás de otra y en el mismo, de esta forma JavaScript Remoting optimiza las solicitudes que se ejecutan y se evitan problemas de concurrencia. El valor por defecto es true. Si configuramos su valor como false, Salesforce lanza una petición independiente por cada invocación de método remoting que hagamos, pero debemos prestar mucha atención en las peticiones que realizamos, porque podemos tener problemas de concurrencia.
    • escape (Boolean): se utiliza para “escapar” la respuesta del método de Apex. El valor por defecto es true.
    • timeout (Integer): es tiempo de espera para la solicitud, en milisegundos. El valor predeterminado es 30000 (30 segundos). El máximo es de 120000 (120 segundos).

Un ejemplo de código en la página Visualforce sería el siguiente:

/*** Página Visualforce ***/

TicMindController.getMessages(
    parameter1, parameter2, etc...,
    function(result, event) {
        if (event.status) {
            // El proceso se ha realizado correctamente.
            // Analizamos el resultado aquí (result).
        } else {
            // Se he producido algún error.
            // Analizamos el error aquí (event.message, event.where)
        }
    },
    {escape:false}
);

Donde:

  • TicMindController: es el nombre de la clase del controlador APEX.
  • getMessages: es el nombre del método APEX que invocamos.

El código de ejemplo del controlador APEX sería el siguiente:

/*** Controlador APEX ***/

public with sharing class TicMindController
    @RemoteAction
    public static String getMessages(String parameter1, String parameter2, etc...){
        // Procesamos y devolvemos la respuesta
    }
}

Algunas anotaciones sobre el código del método APEX remoto:

  • El método remoto debe tener la anotación @RemoteAction.
  • El método remoto debe ser estático.
  • El método remoto debe ser público o global, según sea el alcance de nuestro método. Si el método es global, debemos tener en cuenta que la clase también debe ser global.
  • Si vamos a invocar métodos remotos en alguna página Visualforce incrustada dentro de un iframe, es importante tener en cuenta que los métodos remotos deben de ser globales, sino, se producirán errores, como los siguientes:
    • Visualforce Remoting: Javascript proxies were not generated for controller TicMindController: may not use public remoted methods inside an iframe (este mensaje es muy claro).
    • Unable to invoke action ‘TicMindController.getMessages’: no controller and/or function found.

Existe otra forma de invocar a los métodos remotos desde JavaScript, y es usando directamente el API Remoting de Visualforce. En el siguiente ejemplo podemos ver la misma llamada remota explicada anteriormente, pero usando el API de Visualforce. Se puede apreciar que no existen grandes cambios entre ambas sintaxis:

/*** Página Visualforce ***/

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.TicMindController.getMessages}',
    parameter1, parameter2, etc...,
    function(result, event) {
        if (event.status) {
            // El proceso se ha realizado correctamente.
            // Analizamos el resultado aquí (result).
        } else {
            // Se he producido algún error.
            // Analizamos el error aquí (event.message, event.where)
        }
    },
    {escape:false}
);

Como he indicado anteriormente, los métodos remotos admiten como parámetros no sólo los tipos primitivos APEX, sino también colecciones, sObjects genéricos y tipados, y clases e interfaces APEX definidas por el usuario. Un buen ejemplo de cómo se trabaja con algunos de estos tipos de parámetros lo podemos encontrar en este blog.

Funcionalidad no documentada de JavaScript Remoting

Ante todo tengo que indicar que al ser una funcionalidad no documentada, no es oficial, y puede ser modificada por Salesforce sin previo aviso. Su uso es bajo nuestra propia responsabilidad.

A veces puede que se produzcan problemas de conexión con los servidores de Salesforce desde nuestra máquina. Lo normal es que sean micro cortes de nuestra propia red, y no tengamos acceso momentáneamente a Salesforce. Si esto ocurre en una invocación de un método remoto, se lanzaría una excepción y el proceso finalizaría, ya que el API de Visualforce no vuelve a intentarlo, lo cual a veces es un problema. Para forzar que se realicen un número determinado de reintentos ante esta situación, existe, dentro del API de Visualforce, la siguiente variable llamada maxRetries ,que podemos encontrar aquí:

  • Visualforce.remoting.last.maxRetries: el valor por defecto es 0. Si queremos que se realicen 3 reintentos por ejemplo, debemos asignar a la variable un 2, ya que el contador comienza en 0.

Insisto en que no es oficial, y en la siguiente release que haga Salesforce puede que esto cambie. A fecha de hoy está probado con la Winter 14.

API propio para el uso de JavaScript Remoting

Hace tiempo creé un API propio para poder optimizar en la mayor medida posible el uso que hago de esta técnica de Salesforce. Como podréis observar en el código, dentro de la función que maneja la respuesta del método remoto, escribo en la consola del explorador el tiempo que se ha tardado en completar la solicitud. Esto es más que nada informativo, pero nos puede dar una idea de la velocidad de la red y el tiempo de proceso interno de Salesforce en ejecutar el método.

Además, podéis observar un pequeño API JavaScript para escribir en la consola del explorador y para guardar las excepciones que se producen en un objeto de Salesforce, más que nada para tener registrados todos los errores que se producen en nuestra aplicación, sobre todo en la fase de desarrollo y fase beta.

Aquí os dejo mi código por si a alguno le pueda venir bien:

API JavaScript para escribir en la consola y registrar las excepciones:

/**************/
/*   DEBUG    */
/**************/
// Namespace del debug y la consola del explorador
ticMind.api.debug = {
    /*** Variables ***/
    isDebug: true,
    console: {
        /*** Métodos ***/
        // Escribimos en la consola del explorador
        log: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.log) {
                window.console.log(this.getMessage(message));
            }
        },
        debug: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.debug) {
                window.console.debug(this.getMessage(message));
            }
        },
        info: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.info) {
                window.console.info(this.getMessage(message));
            }
        },
        warn: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.warn) {
                window.console.warn(this.getMessage(message));
            }
        },
        error: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.error) {
                window.console.error(this.getMessage(message));
            }
        },
        // Devolvemos la hora con milisegundos para la consola
        getTimeString: function () {
            var now = new Date();
            var hours = now.getHours();
            var minutes = now.getMinutes();
            var seconds = now.getSeconds();
            var milliseconds = now.getMilliseconds();
            if (hours < 10) { hours = '0' + hours; }
            if (minutes < 10) { minutes = '0' + minutes; }
            if (seconds < 10) { seconds = '0' + seconds; }
            if (milliseconds < 10) { milliseconds = '0' + milliseconds; }             return (hours + ":" + minutes + ":" + seconds + "." + milliseconds);         },         // Devolvemos el mensaje para la consola, con la app más la hora actual, más el propio mensaje         getMessage: function (message) {             return ('TicMind: ' + this.getTimeString() + ' --> ' + ((message) ? message : ''));
        }
    },
    system: {
        /*** Variables ***/
        // Variable que contiene el método que se va a lanzar para guardar el error en el sistema. Debe de tener 3 parámetros de entrada (description, className, method)
        saveExceptionMethod: null,

        /*** Métodos ***/
        // Guardamos el error en el objeto Exception
        saveException: function(description, className, method, event){
            // Comprobamos si tenemos método
            if (!(this.saveExceptionMethod)){
                ticMind.api.debug.console.error('No se ha configurado el método encargado de registrar los errores en el sistema (ticMind.api.debug.system.saveExceptionMethod). Debe tener los siguientes parámetros de entrada: description, className, method.');
                return;
            }
            if (!(description)){return;}
            if ((event) && (event.message)){
                description += '. Excepción producida: ' + event.message + ((event.where) ? '. Traza: ' + event.where : '');
            }
            // Lanzamos método
            this.saveExceptionMethod(description, className, method);
        }
    }
};

API JavaScript para ejecutar métodos remotos:

/**************/
/*  REMOTING  */
/**************/
// Namespace remoting
ticMind.api.remoting = {
    /*** Variables ***/
    buffer: true,
    timeout: 30000,

    /*** Métodos ***/
    // Modificamos el número máximo de reintentos (por defecto 0). 2 sería 3 intentos, contando el 0 como el primero
    setMaxRetries: function (maxRetries) {
        if ((typeof Visualforce != typeof undefined) && (Visualforce.remoting) && (Visualforce.remoting.last) && (Visualforce.remoting.last.maxRetries)) {
            Visualforce.remoting.last.maxRetries = maxRetries;
        }
    },
    // Invocamos el método remoting
    invokeAction: function (action, params, callbackOK, callbackKO, escape) {
        // Si no tenemos acción, salimos
        if (!(action)) {
            ticMind.api.debug.console.log('Error al invocar el método remoto en ticMind.api.remoting.invokeAction. No vienen datos en el parámetro "action"');
            return;
        }

        // Si no tenemos la librería remoting cargada
        if ((typeof Visualforce == typeof undefined) || (!Visualforce.remoting) || (!Visualforce.remoting.Manager) || (!Visualforce.remoting.Manager.invokeAction)){
            ticMind.api.debug.system.error('No existe el método Visualforce.remoting.Manager.invokeAction() en ticMind.api.remoting.invokeAction(). Posiblemente no se haya cargado la librería de Salesforce de Remoting.');
        }

        // Creamos array de argumentos
        var args = [];

        // Añadimos acción
        args[args.length] = (action);

        // Añadimos parámetros
        if (params) { args.push.apply(args, params); }

        // Añadimos debug
        args[args.length] = (ticMind.api.debug.isDebug);

        // Comenzamos temporizador para ver lo que tarda en ejecutarse el método remoting
        var startTime = (new Date()).getTime();
        var dateTimeDiff = null;

        // Añadimos función de respuesta. En la llamada a la callback añadimos un segundo parámetro con el tiempo invertido en la llamada remota
        args[args.length] = function (result, event) {
            // Tiempo empleado en la llamada remoting
            dateTimeDiff = (new Date()).getTime() - startTime;

            // Evaluamos resultado
            if (event.status) {
                // OK
                if ((result == null) || (typeof result.success == (typeof undefined))) {
                    // Resultado OK
                    if (callbackOK) {callbackOK(result, dateTimeDiff);}

                    // Debug
                    ticMind.api.debug.console.log('OK --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Resultado: ' + result);
                }else{
                    // En result viene el objeto con los campos (success, data, error)
                    if (result.success){
                        // Resultado OK
                        if (callbackOK) {callbackOK(result.data, dateTimeDiff);}

                        // Debug
                        ticMind.api.debug.console.log('OK --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Resultado: ' + result.data);
                    }else{
                        // Hubo algún error
                        if (callbackKO) {callbackKO(result.error, dateTimeDiff);}

                        // Si tenemos debug, registramos el error
                        ticMind.api.debug.console.log('KO --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''));

                        // Registramos el error en el sistema
                        var arr = action.split('.');
                        var className = action;
                        var method = action;
                        if (arr.length == 2){
                            className = arr[0];
                            method = arr[1];
                        }
                        ticMind.api.debug.system.saveException('Error al invocar el método remoto "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''), className, method);
                    }
                }
            } else {
                // Error
                if (callbackKO) { callbackKO(event.message, dateTimeDiff); }

                // Si tenemos debug, registramos el error
                ticMind.api.debug.console.log('KO --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''));

                // Registramos el error en el sistema
                var arr = action.split('.');
                var className = action;
                var method = action;
                if (arr.length == 2){
                    className = arr[0];
                    method = arr[1];
                }
                ticMind.api.debug.system.saveException('Error al invocar el método remoto "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''), className, method);
            }
        };

        // Añadimos escape
        var esc = (typeof escape != typeof undefined) ? escape : true;
        args[args.length] = { escape: esc, buffer: this.buffer, timeout: this.timeout };

        // Invocamos método remoto
        Visualforce.remoting.Manager.invokeAction.apply(Visualforce.remoting.Manager, args);
    }
};

Y un ejemplo de uso de mi API Remoting sería el siguiente:

ticMind.api.remoting.invokeAction('TicMindController.getMessages',
                                   [
                                       parameter1,
                                       parameter2,
                                       parameter3,
                                       etc...
                                   ],
                                   function(result, dateTimeDiff){ // Callback OK },
                                   function(error, dateTimeDiff){ // Callback KO },
                                   false);

Como parámetros del método invokeAction tenemos los siguientes:

  • action: nombre del controlador seguido del nombre del método APEX  remoto a invocar.
  • params: array de parámetros a pasar al método APEX.
  • callbackOK: función a la que se llama cuando el método remoto se ejecuta de forma correcta. Esta función recibirá 2 parámetros, result (resultado) y dateTimeDiff (tiempo en milisegundos en ejecutarse el método).
  • callbackKO: función a la que ha producido algún error al ejecutarse el método remoto. Esta función recibirá 2 parámetros, error (mensaje de error) y dateTimeDiff (tiempo en milisegundos en ejecutarse el método).
  • escape: indicamos si se va a escapar el resultado (true, false).

Si quisiéramos aumentar a 3 el número de reintentos en la llamada remoting, tendríamos que hacer lo siguiente:

ticMind.api.remoting.setMaxRetries(2);

Para deshabilitar el buffer de llamadas remoting:

ticMind.api.remoting.buffer = false;

Para aumentar el timeout al máximo de 2 minutos:

ticMind.api.remoting.timeout = 120000;

Y aquí concluye este post. Espero que no os haya resultado muy pesado de leer… y cualquier duda, ya sabéis, en ticMind estamos para ayudaros.