La pinza

Uno de los grandes logros en la estrategia militar de los alemanes en la segunda guerra mundial fue utilizar el movimiento de pinza con embolsamiento (no había forma de salir de la misma) para acabar de manera rápida con un enemigo que se creía muy bien guarnecido en su territorio.

Este es un movimiento que ya se describe desde tiempos inmemoriales, Tsun Zu ya lo recomienda, pero sin embolsamiento, dejando una salida al enemigo para que no se sienta acorralado y se vea forzado a una lucha más feroz. Y, diréis vosotros, ¿porque nos habla este ahora de un movimiento militar? Bien, porque este movimiento se utiliza en muchos otros campos, uno de ellos, y de actualidad tras las últimas elecciones municipales, es el campo político.

Yo ya os he dicho que soy más progresista que otra cosa, no me importa decirlo aunque no creo en eso de que haya que odiar al que piensa distinto y odio la polarización en la política y en cualquier otro aspecto de la vida. Estamos asistiendo, no obstante, a un espectáculo vergonzoso de prostitución de la democracia en la que los ciudadanos, por el momento, no podemos más que escandalizarnos y desanimarnos. Perdonadme por poner un post de corte político en medio de mi blog, pero es que es mi blog y es el sitio donde dejo lo que me pasa por la cabeza (sea técnico o no).

Estas elecciones municipales han servido para varias cosas, que se han visto desde la campaña y desde antes, a saber:

  • Los medios de comunicación ya no tienen ningún pudor en mostrar a quién apoyan y a quien quieren silenciar. Eso es bastante normal cuando hablamos de una tertulia política, pero es vergonzoso cuando convierten los programas de entretenimiento en adoctrinamiento. Solo hay que ver el programa de Pablo Motos y sus invitados a quién menos gracioso o el hecho de que el horario de la telebasura del corazón la hayan sustituido por telebasura política con Ana Rosa, por poner dos ejemplos del lavado sistemático de cerebros débiles.
  • No existen medios de izquierda ni que se le parezcan. La falsa sensación de que la sexta, por ejemplo, era una televisión «friendly» con la izquierda se ha tornado, como no podía ser de otra manera, en la constatación de que tienen patrones a los que contentar y sus directivos no tienen ningún escrúpulo en participar de las cloacas o de invitar a «hostiles» y silenciar a los «amigos» de la izquierda más irreductible.
  • La derecha no tiene escrúpulos y no hace prisioneros. Lo hemos visto en la defenestración de casado, en el uso de «medios» mentirosos y difunde bulos (okdiario, EDA, etc.) siendo pagados sin rubor con dinero público y compitiendo en quien decía la mentira más gorda (ya sabes, si alguien dice que es mentira es que es un zurdo intentando coartar mi libertad de expresión) y siempre es un buen momento para reirse de los progres.
  • No hay aliados entre los que piensan igual que tu. Sumar, que podría parecer una buena idea a priori, se ha esforzado muy mucho en desligarse de Podemos desde el primer momento. Yolanda Díaz, que está en el gobierno gracias a Podemos, que ha disfrutado de libertad y apoyo constante para su labor de gobierno ha decidido que no quiere cargar con el peso de un partido ya formado, que prefiere alimentarse de estructuras más tradicionales, desmembrarlas y someterlas a un único objetivo: volver a sentarse en un gobierno.
  • Nadie se lee los programas… Votan lo mismo al defensor de los toros y azote de los menas que a la que va de bar en bar sirviendo cervezas y sin encontrarse con el ex-novio. ¿Programa? ¿Para qué? Ya se que me roban, pero yo nunca permitiría que me robase un comunista (Álvaro Ojeda dixit)

Esto ha generado una terrible pinza donde, si te unes a Sumar renuncias a cualquier posibilidad de apoyar políticas más sociales (nada que no haría el PSOE) y, además, sabes que te van a mirar mal como el radical que ya no sabe donde meterse. Si no te unes eres un traidor a la izquierda y solo piensas en tu orgullo y les vas a hacer perder votos. Por otro lado tienes a la derecha lobotomizadora que te está diciendo que no merece seguir votando, total, ellos ya han ganado y «la resistencia es futil», mejor dejar de resistirse y, en todo caso, votar al PSOE (que es lo que Pedro Sanchez ha dado por descontado que hará la mayor parte de la gente como voto útil visto lo que provoca la fragmentación del voto).

Lo que más me duele es que gente aparentemente culta e inteligente como Pedro Vallin van por ahí insultado, ninguneando y mofándose de los que han construido el modo de política que ahora pretenden aprovechar para hacerse un Carmena. Lo que ellos no saben es que, al igual que matar la sanidad pública hará que la privada lo pase muy mal (no está preparada para tener que hacerse caso de las cosas costosas), matar a Podemos hará que la izquierda vuelva a lo de siempre, pero de mal humor. Sumar será la nueva Izquierda Unida (¿Similitudes en la intención del nombre?) y PSOE volverá a aglutinar el voto no de derechas.

Internacionalizando javascript en symfony

En este nuevo desafío que he asumido recientemente me encuentro con tener que mantener y mejorar aplicaciones web que están desarrolladas sobre symfony 5. Este es un framework PHP (si, php, no me matéis) que tiene sus cosas buenas y sus cosas malas. Una de las buenas es que permite internacionalizar las aplicaciones sin demasiados problemas… Al menos la parte de html.

Symfony utiliza un sistema de plantillas twig que es donde se pueden realizar las traducciones poniendo {%trans%}cadena{%endtrans%} y luego simplemente tenemos que mantener los archivos de traducción de cada lenguaje y ya toda la magia sucede internamente y sin problemas.

Imagen generada por AI cuando le pedí que me hiciese una imagen para un post de programación

Pero, ¿Qué pasa con las cadenas que generamos en javascript? Los archivos .js se sirven estáticamente sin que pasen por el proceso de sustitución que se hace en los twig, no nos vale poner {%trans%}cadena{%endtrans%} en el js porque ese archivo nunca va a ser tratado por symfony ni por php ya puestos.

La solución que he terminado por implementar se basa en generar un archivo .js con las traducciones en base a una plantilla twig y cargarla cada vez que necesite traducir algo en un js. Para ello solo tenemos que dar estos pasos:

  1. Crear la ruta en routes.yml (o usar un decorador) para /jstrans.js o similar
  2. Crear un controlador para servir ese archivo, por ejemplo JavascriptController.php
  3. Crear un twig para el javascript, por ejemplo jstrans.js.twig
  4. Meter en el twig todas las cadenas y sus traducciones correspondientes
  5. Hacer que jstrans.js se cargue en todas las páginas que necesiten traducciones
  6. usar la función que traduce en los js que lo necesiten.

Aquí os dejo un ejemplo de cada uno de los archivos:

JavascriptController.php

class JavascriptController extends AbstractController
{
    public function __construct()
    {
        $this->config = new Config();
    }

    public function index(): Response
    {
        $response =  $this->render('javascript/jstrans.js.twig', [
            'controller_name' => 'JavascriptController',
        ])->setSharedMaxAge(3600)->setPublic();
        $response->headers->set('Content-Type','text/javascript');
        return $response;
    }
}

jstrans.js.twig

if (typeof jstrans == 'undefined') {

    var jstrans = {
        traducciones: {
            'Seleccionar': '{%trans%}Seleccionar{%endtrans%}',
            'Registros': '{%trans%}Registros{%endtrans%}',
            'No se han encontrado resultados': '{%trans%}No se han encontrado resultados{%endtrans%}',
        },
        // Función que traduce un texto
        translate: function (text) {
            if (jstrans.traducciones[text] != undefined) {
                return jstrans.traducciones[text];
            } else {
                return text;
            }
        }
    };
} else {
    console.log ('jstrans ya está definida');
}

Luego ya se meten las cadenas en, messages.en.yaml,, por ejemplo

Seleccionar: Select
Registros: Registers
No se han encontrado resultados: No results found

Y luego solo hay que usarlo en los archivos js de esta manera:

jstrans.translate("Seleccionar")

Y eso te dará la cadena traducida al idioma en el que se esté navegando.

¿Conoces algún método mejor para conseguir traducciones en los js de un proyecto symfony? déjame un comentario si es así…

Buscar talento en linkedin

Durante mi periodo de CEO en biblioeteca he tenido la necesidad de contratar personas que pudiesen ayudarnos con los proyectos que nuestros clientes nos proponían. En este mercado la rotación es muy alta y el talento (aunque cueste admitirlo) bastante escaso. Tampoco es que nosotros tengamos necesidades extraordinarias, y la mayor parte de nuestras contrataciones venían recomendadas de antes (eso siempre es lo mejor), pero cuando hay que cubrir puestos rápidamente es cuando nos encontramos en problemas.

Uno de los sitios a los que he acudido a ofrecer puestos de trabajo ha sido a LinkedIn, en concreto creo que conseguí un total de 4 personas (en un periodo largo), pero cada vez ha estado funcionando peor, os comento el ejemplo de la última campaña donde buscaba alguien con Java y Linux (y tampoco pedía años de experiencia). El anuncio en concreto está en este enlace:

https://www.linkedin.com/jobs/view/3517177902/

Para esta oferta recibí estos resultados:

Recibí un total de 7 cvs, de los que ninguno era adecuado del todo.

Pero hagamos unas pequeñas cuentas… 100 Euros de gasto, 96 visualizaciones, precio por visualización 1,04 Euros… ¡Por cada visualización! Esto, por si no os lo parece es carísimo. Calculemos que por cada 100 visualizaciones obtenemos 5cvs (1 de cada 20, esto es ser muy optimista), si de cada 10cvs obtenemos 1 al que entrevistar y que, de media, hay que entrevistar a 5 candidatos por puesto… Nos queda que necesitaríamos un mínimo de 5*10*20 = 1000 visualizaciones.

LinkedIn ¿me quieres decir que para contratar un programador (que tampoco busco un experto) tengo que gastarme 1.040 Euros?

A mi esto me ha parecido un error, y como tal se lo he preguntado a la amable gente de linkedin en twitter…

Qué majos! .. Vamos a ello…

Umm, no es ninguna de esas opciones, vamos a abrir incidencia

Vamos a ver que mi pregunta NO es sobre LinkedIn ADS, que de eso no tengo cuenta.. A ver si hay otra forma…

Pues no, no ayuda la verdad…

Y esto tampoco… ¿Cómo que las visitas disminuyen con el tiempo? Oye, que os habéis comido 100 pavos en dos días y solo me habéis dado 96 visualizaciones… Algo pasa

Y chin-pum ahí se acaba la interacción con la ayuda de linkedin… plasplasplas

Así que, si me preguntáis a mi, LinkedIn ha dejado de ser un sitio asequible para poner anuncios de trabajo…

Ser empresario también es tener que tratar con malnacidos

Como se diría en twitter, acompañemé a conocer esta triste historia…

Luis Carballo Rua con nuestro portatil

Llevo 19 años siendo empresario y he contratado unas cuantas decenas de personas. Antes era más sencillo dar con alguien competente, pero ahora con tanto bootcam y con tanto FP con esteroides es más complicado saber si un trabajador sabe lo que dice que sabe o no. Antes tener un título significaba un mínimo, ahora tienes que bucear para encontrar si la persona en cuestión sabe lo que es el http o le han dicho que eso es magia de Angular. En fin, que se te pueden colar inútiles sin comerlo ni beberlo.

Uno de esos casos me pasó recientemente, una persona respondió a un anuncio de trabajo que pusimos en LinkedIn. La plaza que pedíamos de desarrollador front la terminamos ocupando con otra persona, pero me pareció interesante el cv que nos pasó, lleno de dibujos hechos por él mismo.

El caso es que en la siguiente plaza para full stack se volvió a presentar, decidimos entrevistarle y, quitando que hablaba muy deprisa, nos dijo que si a todo lo que le preguntamos, no parecía perdido en ningún área de los que preguntamos y su CV era molón, así que le hicimos una oferta a los dos días… Oferta que rechazó porque dijo que ya estaba trabajando en otra empresa que le llamó después de nosotros… En fin, cosas que pasan, a seguir entrevistando.

Esta vez el proceso se hizo un poco más largo y a eso de las dos semanas me contacta de nuevo diciendo que las condiciones de la nueva empresa no le interesan y que si le mantengo la oferta. Yo, que soy un hombre de palabra, le digo que le mantengo exactamente la misma oferta que le hice… Y, dicho y hecho, le preparamos el contrato y quedamos ya para que empiece a trabajar en remoto, básicamente, aunque esperábamos que al menos el primer día se quedase en la oficina para que le explicásemos las cosas.

Primer día: quedamos a las 9:30 porque yo tenía reuniones antes… Espero, se hacen las 10, las 11.., le mando un whatsapp, no me contesta, a las 12 ya le llamo por teléfono y me dice que está en Alcorcón, pero llevando su madre al médico, que luego se pasa por aquí (no me parece lo mejor para el primer día y, además, sin avisar), en fin, que a las 13 se pasa por la oficina, firma el contrato, le enseño a entrar en el portatil y en su cuenta de correo; y… Se va. ¡¡!! Que si, que es un trabajo remoto, pero leche, no solo llegas 4 horas tarde sino que ni siquiera te quedas a que te expliquemos nuestra forma de trabajar…

La primera semana de trabajo se perdió intentando que arrancase solo en ubuntu el portatil y que no usase windows (windows=kk), le compramos hasta una docking station para que pudiese enchufar sus dos monitores (y se lo tuvimos que mandar a casa, que él pasar por l oficina ya le daba pereza), la segunda semana ya empieza a hacer algo productivo pero sin hacer puto caso de lo que le decimos. Nos llena el código de librerías que luego tenemos que borrar y nos quita más tiempo del que ahorramos. En la cuarta semana le ponemos a hacer algo para despliegue inmediato (tampoco era muy complicado, pero dediqué yo más horas que él) y nos dice que se va a vivir a la costa que, total, es remoto y no nos vamos a enterar… Y el pavo se va de mudanza el mismo día que pasamos a producción grrrrr. Obviamente la cosa peta y tengo que arreglarlo yo solo.

Lo primero que hace nada más llegar a su destino es pedirme un anticipo (estábamos a día 7 de mes), cosa que me extraña pero que procedemos a hacer sin ningún problema. A partir de ese momento su rendimiento fue cero. La lista de problemas (en orden cronológico):

  • Que el proveedor de internet que tenía no le permitía usar el puerto del ssh y no podía subir nada al servidor
  • Que los que han venido a repararlo no tenían idea y ha terminado tirando el router por la ventana
  • Que me he quedado dormido (excusa para no aparecer por el daily) y el movil estaba descargado
  • Que se me acaban los datos del móvil
  • Que me van a poner la fibra (ese día desapareció tras el daily y apareció después de haber terminado la jornada diciendo que se había dejado el movil en casa mientras le instalaban la fibra)
  • Que me he cargado el móvil (caída fortuita al suelo, dice)
  • Que sigue sin fibra y tiene que hacer el daily desde un bar
  • Que el del bar ha abierto más tarde hoy y no he podido hacer el daily

Y mientras el trabajo que tenía asignado ahí pudriéndose y teniendo que hacerlo entre sus compañeros y yo…

Llegados a este punto decido que es suficiente y que, obviamente, no ha superado el periodo de prueba. Se lo comunico y le indico que empaquete el portatil, la mochila y la docking station y que alguien pasará a recogerla.

Y ahora salta la sorpresa en las gaunas… Esta persona decide que va a quedarse con el material de la empresa, material que vale más que lo que él ha generado para la empresa. Esto no solo está tipificado como una apropiación indebida sino que dado el valor de lo apropiado puede ponerle en un grave aprieto. Mi empresa, como no puede ser de otra manera, le ha pagado finiquito y liquidación (muchas tentaciones de quedarnos con ese dinero en compensación, pero mi abogado me recomendó pagarlo de todas formas) y este desalmado malnacido no ha dado señales de vida.

Ya es duro mantener y gestionar una empresa como para tener que lidiar con gentuza de esta calaña. No solo ha perjudicado a sus compañeros sino que te deja una mala leche que terminas pagando con el primero que pasa… En fin, solo espero que el karma haga su trabajo. Si alguien quiere contratar a un full-stack developer, que me pregunte a quien no contratar nunca.

JWT y Postman

O, mejor dicho, como probar tus APIs que usen JWT en postman.

Cuando estamos diseñando APIs es muy normal que, lo primero, las diseñemos sin pensar en el tema de la seguridad y, una vez que están funcionando, ya les añadimos los elementos de seguridad para que no puedan ser llamadas por cualquiera en producción.

Una forma, que yo he usado mucho, para fortificar las llamadas es hacerlo mediante un token que se ha obtenido con las credenciales de un usuario concreto, eso supone hacer una llamada previa al sistema del que se obtiene el token y, después, verificarlo en cada llamada. Esto, siendo un token arbitrario, supone sobrecargar el sistema de seguridad con llamadas de comprobación de token, además, tenemos que hacer caducar los tokens periódicamente ya que cualquier filtración del mismo da acceso a los recursos del usuario durante el tiempo que el token esté sin caducar.

Otra manera, ligeramente diferente, es utilizar JWT (JSON Web Tokens) que se diseñaron para poder transmitir credenciales y otra información entre sistemas de una manera más eficiente y segura. Lo único un poco «especial» es que tiene que haber un secreto compartido entre los sistemas que se comunican. Además, se usa ese secreto para firmar la comunicación, así que no es sencillo generar el token JWT de forma manual para las pruebas.

Si usas postman (que es lo que estoy usando yo últimamente) para hacer las pruebas, hay una manera de solventar el problema y que, básicamente, pasa por rellenar el campo pre-request script con este código:

// JWT generation script adapted from
// https://gist.github.com/corbanb/db03150abbe899285d6a86cc480f674d

var jwtSecret = pm.environment.get('jwt_secret') || ''

// Set headers for JWT
var header = {
	'typ': 'JWT',
	'alg': 'HS256'
};

// Prepare timestamp in seconds
var currentTimestamp = Math.floor(Date.now() / 1000)

var data = {
	'iss': pm.environment.get('jwt_iss') || '',
	'ist': pm.environment.get('jwt_ist') || '',
	'iat': currentTimestamp,
	'exp': currentTimestamp + 30, // expiry time is 30 seconds from time of creation
	'jti': 'jwt_nonce'
}


function base64url(source) {
    // Encode in classical base64
    encodedSource = CryptoJS.enc.Base64.stringify(source)
    
    // Remove padding equal characters
    encodedSource = encodedSource.replace(/=+$/, '')
    
    // Replace characters according to base64url specifications
    encodedSource = encodedSource.replace(/\+/g, '-')
    encodedSource = encodedSource.replace(/\//g, '_')
    
    return encodedSource
}

// encode header
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header))
var encodedHeader = base64url(stringifiedHeader)

// encode data
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data))
var encodedData = base64url(stringifiedData)

// build token
var token = `${encodedHeader}.${encodedData}`

// sign token
var signature = CryptoJS.HmacSHA256(token, jwtSecret)
signature = base64url(signature)
var signedToken = `${token}.${signature}`

pm.environment.set('jwt_signed', signedToken)
console.log('Signed and encoded JWT', signedToken)

En el mismo vemos que se usan estas variables que deberemos incluir en el entorno o en la petición:

  • jwt_secret : secreto compartido con el otro sistema
  • jwt_iss: issuer (como queramos identificar al emisor)

En cualquier caso también podríamos añadir el campo sub si es parte del payload que recibe nuestro API, o cualquier otro campo adicional que decidamos incluir, ya que el proceso de firma se realiza en ese momento y las fechas de expiración se actualizan justo antes de lanzar la petición al API por lo que no estarán expiradas en las pruebas.

Lo único que quedaría pendiente sería usar el token generado en la cabecera (o donde corresponda) ya que este código genera la variable {{jwt_signed}} esta la podemos usar donde queramos.

Sigo buscndo formas más sencillas para hacer las pruebas sin salir del Visual Studio Codel, si las encuentro las pondré por aquí.