Skip to content

Mariano Rodrigo

AI Solutions Engineer building production systems with artificial intelligence, automation, and full-stack architecture. This is my public engineering lab: architecture decisions, implementation reports, experiments, and lessons from real systems.

Reconstruir el Save to PDF de LinkedIn: una extensión que corre entera en el cliente

LinkedIn Save to PDF: de un perfil a un PDF listo para reclutadores en un click

LinkedIn quitó, sin ruido, su opción “Save to PDF”. Para cualquiera que busca trabajo, recluta o simplemente archiva un perfil, desapareció una exportación de un click. Así que la reconstruí, como extensión de Chrome open-source, con una restricción firme: tenía que correr entera en el cliente. El objetivo no era solo traer la función de vuelta, sino terminar con una exportación mejor que la propia de LinkedIn.

La restricción que definió todo

Sin servidores, sin cuentas, sin tracking. Cada paso, leer el perfil, construir el layout, generar el PDF, pasa dentro de tu browser. El único almacenamiento que toca es chrome.storage.local, usado brevemente para pasarle los datos extraídos a la vista de impresión, y borrado en el momento en que se produce el PDF. No se recolecta ni se transmite nada.

Esa restricción no es una frase de marketing, es una decisión de arquitectura. Un perfil es dato personal. En el momento en que lo mandás a un servidor para “procesarlo”, te quedás con un problema de privacidad: logs, retención, superficie de brecha, una política de privacidad que cumplir. Mantener todo local elimina esa clase entera de problema. El trade-off es que el browser tiene que hacer todo el trabajo, sin un backend en el que apoyarse.

La parte difícil: sin API, un DOM que se mueve, y truncación

LinkedIn no tiene una API pública para esto, así que la extensión lee la página renderizada mediante content scripts. Eso viene con el dolor de siempre: el DOM cambia, el markup está profundamente anidado, y el perfil se renderiza en el idioma que use la cuenta. El parseo es estructural y no basado en texto, así que funciona con perfiles de LinkedIn en cualquier idioma.

El problema más duro es la truncación. La página principal del perfil muestra solo las primeras entradas de experiencia, educación y skills; el resto queda detrás de “Mostrar todo”. Para capturar todo, el Full Profile Export opcional abre las páginas /details/<sección>/ del mismo perfil en pestañas temporales de fondo, una por vez, lee las listas completas y las cierra. Es más lento y es opt-in, pero recupera las secciones que una exportación simple perdería.

Generar el PDF

No hay librería de PDF empaquetada. La extensión construye una vista limpia optimizada para impresión y usa el print-to-PDF nativo del browser. Eso mantiene la extensión chica, evita enviar un motor de renderizado, y produce una salida basada en texto, seleccionable y ATS-friendly en vez de una imagen aplanada. La vista de impresión renderiza en modo claro para resultados consistentes, y elegís qué secciones incluir: foto, contacto, experiencia, educación, skills.

Hay un ángulo ATS también. El PDF original de LinkedIn fue diseñado para imprimir, no necesariamente para applicant tracking systems, y su layout de dos columnas es justo el tipo de estructura que esos parsers sufren. La extensión, en cambio, genera un documento simple de una columna con texto seleccionable, más fácil de leer en orden tanto para humanos como para parsers de ATS.

Un click, o el perfil completo

La extensión trae dos modos, y ayuda alinearlos contra el propio PDF de LinkedIn. El export de 1-click es instantáneo y cubre lo esencial. El Deep export abre las páginas truncadas /details/ para reconstruir el perfil completo. El PDF nativo de LinkedIn queda en el medio, en un layout de dos columnas que los parsers de ATS tienden a rechazar. Mismo perfil, tres exportaciones:

PDF de LinkedInExport 1-clickDeep export
Un click, instantáneo
Foto de perfil
Descripciones completas de cada rol
Todas las skills, no solo las primeras
Todos los idiomas
Elegir qué secciones incluir
Una columna, ATS-friendly
Generado localmente, nada sale del browser

Feature por feature, la extensión produce el mejor documento. El Deep export es estrictamente más completo que el PDF de LinkedIn: conserva la foto y recupera cada descripción de rol, cada skill y cada idioma, y sale como texto limpio, de una columna, ATS-friendly y generado entero en tu máquina. La única ventaja que le queda a LinkedIn es el click instantáneo, y eso es exactamente lo que iguala el modo 1-click. Así tenés las dos cosas: instantáneo cuando querés velocidad, completo cuando querés todo, y en ningún caso tu perfil sale del browser.

Las decisiones de ingeniería

Para quien construye, las piezas son deliberadamente pocas. Es una extensión Manifest V3, y cada pieza tiene un solo trabajo:

  • Content scripts leen el perfil renderizado directo de la página.
  • Un service worker de fondo orquesta el Full Profile Export, abriendo y cerrando pestañas temporales una por vez.
  • chrome.storage.local transporta brevemente los datos extraídos a la vista de impresión, y después se limpia.
  • window.print() dispara el print-to-PDF nativo del browser, así no se envía ningún motor de render con la extensión.
  • Selectores estructurales e independientes del idioma apuntan al DOM por forma y no por texto, así el parser sobrevive a un perfil en cualquier idioma.
  • Un set de permisos acotado, activeTab, storage, scripting y tabs, restringido a https://*.linkedin.com/* y nada más.

Cualquiera puede auditar exactamente qué puede tocar, algo que importa para una herramienta que maneja datos de perfil. El tema recurrente es fallar con gracia: sin una API, cada selector es una apuesta sobre cómo renderiza LinkedIn hoy, así que el parser está hecho para degradarse en vez de romperse. Todo es open source.

Lo que aprendí construyéndola

Construir la extensión me recordó que las extensiones de browser suelen estar más cerca de la ingeniería de sistemas que del desarrollo frontend. Sin una API, cada release depende de entender cómo otra aplicación renderiza su interfaz, anticipar el cambio y diseñar parsers que fallen con gracia. La interfaz es la parte fácil. La difícil es que estás integrándote con una superficie que nadie diseñó para ser integrada, y que se mueve bajo tus pies.

Instalar y código

Conclusión

El objetivo original era simplemente traer de vuelta una función que faltaba. Terminó siendo un ejercicio de arquitectura privacy-first, automatización del browser, parseo de DOM y diseño de software que mantiene los datos personales exactamente donde ya están: en la máquina del usuario. Esa es la parte que me quedaría. Para una herramienta que lee el perfil de alguien, el default correcto es hacer el trabajo donde vive el dato, y no dejarlo salir nunca.