Este portfolio
En línea
La portada: quién es, qué busca y las dos formas de seguir 
Tres destacados a la vista; el resto, a un clic 
El asistente responde sobre el CV, en streaming y con el expediente en el servidor 
Una ficha de proyecto, en tema oscuro
Sitio estático en Astro sobre Cloudflare Pages, trilingüe y sin framework de interfaz. Un solo cv.json alimenta las páginas, el PDF de cada idioma, el JSON-LD y los endpoints para máquinas; lo dinámico son Pages Functions.
- CSP por hashes y sin unsafe-inline, recalculada desde el HTML en cada build.
- Presupuestos de HTML, DOM, CSS y JS que fallan el build; Lighthouse bloquea si el LCP pasa de 2,5 s.
- El PDF se genera y se comprueba en cada build: una hoja A4 y texto extraíble con dos motores.
- Límites de uso atómicos en D1, probados contra una base real.
El problema
Un CV en PDF se queda corto para una candidatura técnica —no se puede enlazar, no lo lee un buscador y no demuestra nada sobre cómo trabajas— pero sigue siendo lo que te piden. Y mantener a mano la web, el PDF y el perfil de cada portal significa que los tres acaban diciendo cosas distintas. La idea era tener una sola fuente y que todo lo demás saliera de ella.
Mi papel
Todo: el diseño, el código, la infraestructura y el contenido. No hay plantilla de partida ni framework de interfaz; lo que se descarga en el navegador son módulos propios.
Decisiones
Un solo `cv.json`, y todo lo demás derivado
El archivo sigue el esquema de JSON Resume y el build valida contra él. De ahí salen las páginas en tres idiomas, un PDF de una hoja por idioma, el JSON-LD de los buscadores y `/cv.json`, `/cv.md` y `/llms.txt`. La alternativa —mantener la web y el PDF por separado— es la que produce currículums que se contradicen, y además obliga a repetir cada corrección tres veces.
La calidad se comprueba en el build, no en la revisión
El build falla si una página se pasa de su presupuesto de HTML, DOM, CSS o JavaScript, si un `<style>` no tiene su hash en la política de seguridad, si el PDF ocupa más de una hoja o si su texto no se puede extraer. Y un trabajo de Lighthouse bloquea el despliegue si la portada pasa de 2,5 s de LCP. Revisar a ojo funciona una vez; un techo que falla funciona siempre, y además explica qué se rompió.
Nada de medir en local: sólo cuenta la CI
Las dos medidas que importan —el LCP simulado y las referencias visuales— dependen del renderizado de texto y de la CPU de la máquina. En local, además, un bloqueador de anuncios filtra las peticiones de cualquier navegador, incluido el de las pruebas. Las referencias se generan en el contenedor fijado de Playwright y Lighthouse corre en la misma imagen en la CI: tres tandas y la mediana.
El asistente deja el expediente en el servidor
Responde preguntas sobre el CV con un modelo, y eso abre dos problemas: lo que se le envía se puede sonsacar, y una API de pago abierta a internet se puede agotar. Lo primero se resuelve no enviando nada que no diría en una entrevista, y escribiendo las reglas en positivo: una regla «no hables de X» publica que X existe. Lo segundo, con límites por IP en D1, atómicos para que dos peticiones a la vez no cuenten como una.
Resultado
- La portada carga en 2,1 s de LCP simulado en la CI, con 21 peticiones y 166 KB. El JavaScript total son 17 KB con gzip, repartidos en nueve módulos propios.
- Cero violaciones de accesibilidad con axe en las seis páginas, y una política de seguridad sin `unsafe-inline` que el build vuelve a calcular desde el HTML servido.
- El mismo currículum se sirve en seis formatos sin escribirlo dos veces: tres idiomas de página, tres de PDF, `/cv.json`, `/cv.md`, `/llms.txt` y el JSON-LD.
Qué cambiaría
Construí de más antes de mirar quién entraba. Hubo una consola interactiva, una vista en Markdown y una paleta de comandos; entre las tres, cuarenta días de datos dieron 69 usos frente a 383 visitas de la portada, y costaban unas 5 500 líneas. Las retiré, y con ellas se fue la mitad del JavaScript. Hoy empezaría por la medición: primero saber qué se usa, y construir lo siguiente con esa respuesta delante.