Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ export default defineConfig({
site: "https://sergiomarquez.dev",
output: "static", // Explicit SSG mode

// Internationalization
i18n: {
defaultLocale: "es",
locales: ["es", "en"],
routing: {
prefixDefaultLocale: false, // / = es, /en/ = en
},
},

// Build optimizations
build: {
format: "file", // Generate files instead of directories for better SEO
Expand All @@ -20,6 +29,10 @@ export default defineConfig({
changefreq: "monthly",
priority: 0.7,
lastmod: new Date(),
i18n: {
defaultLocale: "es",
locales: { es: "es", en: "en" },
},
}),
],

Expand Down
File renamed without changes.
148 changes: 148 additions & 0 deletions public/cv.es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"basics": {
"name": "Sergio Márquez",
"tagline": "Desarrollador IA & Backend | Python, FastAPI, LLMs",
"email": "contacto@sergiomarquez.dev",
"urls": {
"site": "https://sergiomarquez.dev",
"linkedin": "https://www.linkedin.com/in/sergiomarquezp/",
"github": "https://github.com/sergiomarquezdev",
"x": "https://x.com/sergiomarquezp_",
"youtube": "https://www.youtube.com/@sergiomarquezp"
},
"summary": "Desarrollador Backend reconvertido a IA/ML. Tras varios años desarrollando con Java y Spring, he evolucionado hacia la aplicación de un enfoque de ingeniería (APIs, datos, arquitectura y producción) a soluciones de IA usando Python y FastAPI. Me especializo en construir servicios que transforman necesidades de negocio en funcionalidades mantenibles, incorporando pipelines RAG, búsqueda semántica y arquitecturas agénticas. Me centro en que el 'cómo funciona' sea tan sólido como el 'qué hace' mediante automatización y prácticas de ingeniería robustas."
},
"experience": [
{
"company": "VITALY",
"role": "Desarrollador IA/ML",
"period": "May 2025 – Presente",
"summary": "Ingeniero backend aplicando IA a productos reales y herramientas internas, combinando backend, datos y automatización.",
"highlights": [
"Desarrollo de APIs y servicios en Python/FastAPI para procesamiento de datos e integración de sistemas.",
"Construcción de pipelines de ingesta y procesamiento (documentos, vídeo) incluyendo transcripción y validaciones.",
"Implementación de capacidades de IA con LLMs: RAG, embeddings y búsqueda vectorial integrados en middleware productivo.",
"Evolución de asistentes IA: gestión de memoria/sesiones, mejoras de estabilidad y ajuste fino post-despliegue.",
"Automatización de ingesta RAG vía n8n + Python (Sheets -> MongoDB + Pinecone) con deduplicación por hash; ahorro de ~8-10 horas por configuración.",
"Mejora de la tasa de validación de documentos del 70% al 90% usando un ensemble GPT+Claude+Gemini y ajuste de OCR.",
"Migración del chat a arquitectura agéntica (Google ADK) con LiteLLM; ~15% menos latencia y ~35% menos coste de infraestructura.",
"Stack: Python, FastAPI, n8n, GCP, MongoDB, BigQuery/Firestore, Pinecone, Docker, GitLab CI/CD, OpenTelemetry."
]
},
{
"company": "VITALY",
"role": "Desarrollador Full-Stack",
"period": "Jun 2021 – May 2025",
"summary": "Enfocado en especialización backend (Java/Spring) con responsabilidad sobre integraciones cloud/datos y soporte frontend.",
"highlights": [
"APIs Middleware Estratégicas: Diseño y desarrollo de middleware basado en Spring para optimizar la comunicación entre frontend y servicios externos.",
"Migración y Optimización a GKE: Lideré la migración a Google Kubernetes Engine, automatizando la entrega continua y optimizando despliegues.",
"Integración de Datos Post-Fusión: Participación activa en la integración de datos tras la fusión Preving/Cualtis, realizando mapeo y adaptación de aplicaciones.",
"Experiencia Internacional (Dublín, 2022): 3 meses trabajando en remoto desde Irlanda, mejorando fluidez en inglés y adaptabilidad en entornos globales.",
"Stack: Java, Spring Framework, Angular, Docker, Kubernetes, GCP, GitLab CI/CD."
]
},
{
"company": "VITALY",
"role": "Desarrollador en Prácticas",
"period": "Abr 2021 – Jun 2021",
"summary": "Formación intensiva y primer contacto con entornos profesionales de desarrollo web.",
"highlights": [
"Formación intensiva en Java Spring, Angular, Docker y Kubernetes.",
"Participación en equipos de desarrollo ágil, contribuyendo a proyectos reales y aprendiendo ciclos de vida completos del software."
]
}
],
"projects": [
{
"name": "One dAIly Blog",
"headline": "Blog autónomo con IA que genera contenido diario sobre Python con Gemini y OpenAI.",
"stack": [
"Astro",
"Node.js",
"Gemini",
"OpenAI",
"PostgreSQL"
],
"url": "https://blog.sergiomarquez.dev",
"github": [
"https://github.com/sergiomarquezdev/one-daily-blog",
"https://github.com/sergiomarquezdev/one-daily-blog-backend"
],
"private": true
},
{
"name": "AI Photo Transformer",
"headline": "Pipeline de IA multietapa para transformaciones fotográficas cinematográficas usando Gemini y Seedream 4.",
"stack": [
"FastAPI",
"Python",
"Gemini",
"Replicate",
"Supabase",
"HTMX"
],
"github": [
"https://github.com/sergiomarquezdev/altoke_image_gen"
],
"private": true
},
{
"name": "Video Transcriber & AI Summarizer",
"headline": "CLI para transcripción acelerada por CUDA con Whisper, resúmenes IA multi-proveedor y generación de contenido para LinkedIn/Twitter.",
"stack": [
"Python",
"Whisper",
"Gemini",
"PyTorch",
"CUDA"
],
"github": [
"https://github.com/sergiomarquezdev/yt-transcriber"
],
"private": false
},
{
"name": "Gesture-Controlled Digital Whiteboard",
"headline": "Pizarra digital con seguimiento de manos en tiempo real usando visión por computador y MediaPipe.",
"stack": [
"Python",
"OpenCV",
"MediaPipe"
],
"url": "https://github.com/sergiomarquezdev/pizarra-digital-python",
"github": [
"https://github.com/sergiomarquezdev/pizarra-digital-python"
],
"private": false
},
{
"name": "Acestream Docker Engine",
"headline": "Solución de streaming containerizada con configuración automatizada para entornos Ubuntu.",
"stack": [
"Docker",
"Shell",
"Ubuntu"
],
"url": "https://github.com/sergiomarquezdev/acestream-docker-home",
"github": [
"https://github.com/sergiomarquezdev/acestream-docker-home"
],
"private": false
}
],
"certifications": [
{
"name": "EF SET English Certificate 76/100 (C2 Proficient)",
"issuer": "EF SET",
"date": "2024-01-03",
"url": "https://www.efset.org/cert/Sp9SYB"
},
{
"name": "Master en Frameworks Javascript: Aprende Angular, React, Vue",
"issuer": "Udemy",
"date": "2023",
"url": "https://www.udemy.com/certificate/UC-873b8fa3-dd9a-40a3-8185-073a5e6df250/"
}
]
}
30 changes: 15 additions & 15 deletions src/components/BaseHead.astro
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
---
import { cv } from "../data/cv";
import { getCv } from "../data/cv";
import { getLocale, t } from "../i18n/index";

interface Props {
title: string;
description: string;
}

const { title, description } = Astro.props;
const locale = getLocale(Astro.currentLocale);
const cv = getCv(locale);
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const socialImageURL = new URL("/og-image.svg", Astro.site);
const siteURL = Astro.site ?? new URL("https://sergiomarquez.dev");

const structuredData = {
"@context": "https://schema.org",
Expand All @@ -28,16 +32,7 @@ const structuredData = {
"@type": "Organization",
name: cv.experience[0].company,
},
knowsAbout: [
"Artificial Intelligence",
"Machine Learning",
"Backend Development",
"Python",
"FastAPI",
"RAG Pipelines",
"LLMs",
"Google Cloud Platform",
],
knowsAbout: t(locale, "structured.knowsAbout").split(","),
};
---

Expand All @@ -51,23 +46,28 @@ const structuredData = {
<meta name='generator' content={Astro.generator} />
<link rel='canonical' href={canonicalURL} />

<!-- Hreflang alternate links -->
<link rel='alternate' hreflang='es' href={new URL("/", siteURL)} />
<link rel='alternate' hreflang='en' href={new URL("/en/", siteURL)} />
<link rel='alternate' hreflang='x-default' href={new URL("/", siteURL)} />

<title>{title}</title>

<!-- Open Graph / Facebook -->
<meta property='og:type' content='website' />
<meta property='og:url' content={Astro.url} />
<meta property='og:url' content={canonicalURL} />
<meta property='og:title' content={title} />
<meta property='og:description' content={description} />
<meta property='og:image' content={socialImageURL} />
<meta property='og:image:width' content='1200' />
<meta property='og:image:height' content='630' />
<meta property='og:image:alt' content='Sergio Márquez - AI & Backend Developer portfolio' />
<meta property='og:locale' content='en_US' />
<meta property='og:image:alt' content={t(locale, "og.imageAlt")} />
<meta property='og:locale' content={t(locale, "og.locale")} />
<meta property='og:site_name' content='Sergio Márquez' />

<!-- Twitter -->
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:url' content={Astro.url} />
<meta name='twitter:url' content={canonicalURL} />
<meta name='twitter:title' content={title} />
<meta name='twitter:description' content={description} />
<meta name='twitter:image' content={socialImageURL} />
Expand Down
6 changes: 4 additions & 2 deletions src/components/Certifications.astro
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
---
import type { CvData } from "../data/cv";
import { getLocale, t } from "../i18n/index";
import LinkIcon from "./icons/LinkIcon.astro";

interface Props {
certifications: CvData["certifications"];
}

const { certifications } = Astro.props;
const locale = getLocale(Astro.currentLocale);
---

<div class="certifications">
<h2 class="section-title">Certifications</h2>
<h2 class="section-title">{t(locale, "section.certifications")}</h2>
<ul class="cert-list">
{certifications.map((cert) => (
<li>
Expand All @@ -19,7 +21,7 @@ const { certifications } = Astro.props;
class="cert-card card-interactive"
target={cert.url ? "_blank" : undefined}
rel={cert.url ? "noopener noreferrer" : undefined}
aria-label={`View ${cert.name} certificate`}
aria-label={`${t(locale, "aria.viewCert")} ${cert.name}`}
>
<div class="cert-content">
<h3 class="cert-name">
Expand Down
4 changes: 3 additions & 1 deletion src/components/Experience.astro
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
---
import type { CvData } from "../data/cv";
import { getLocale, t } from "../i18n/index";

interface Props {
experience: CvData["experience"];
}

const { experience } = Astro.props;
const locale = getLocale(Astro.currentLocale);
const MAX_HIGHLIGHTS = 3;
---

<div class="experience">
<h2 class="section-title">Experience</h2>
<h2 class="section-title">{t(locale, "section.experience")}</h2>
<div class="roles">
{experience.map((role) => (
<div class="role-card card-interactive">
Expand Down
53 changes: 53 additions & 0 deletions src/components/HomePage.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
import type { CvData } from "../data/cv";
import About from "./About.astro";
import Certifications from "./Certifications.astro";
import Experience from "./Experience.astro";
import Projects from "./Projects.astro";

interface Props {
cv: CvData;
}

const { basics, experience, projects, certifications } = Astro.props.cv;
---

<div class="sections">
<section id="about" class="section animate-fade-up">
<About basics={basics} />
</section>

<section id="experience" class="section animate-fade-up delay-100">
<Experience experience={experience} />
</section>

<section id="projects" class="section animate-fade-up delay-200">
<Projects projects={projects} />
</section>

<section id="certifications" class="section animate-fade-up delay-300">
<Certifications certifications={certifications} />
</section>
</div>

<style>
.sections {
display: flex;
flex-direction: column;
gap: 4rem;
}

.section {
scroll-margin-top: 2rem;
}

@media (min-width: 1024px) {
.sections {
gap: 6rem;
}

.section {
scroll-margin-top: 6rem;
}
}
</style>
44 changes: 44 additions & 0 deletions src/components/LanguageSwitcher.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
import { getLocale, t } from "../i18n/index";

const locale = getLocale(Astro.currentLocale);
const targetHref = locale === "es" ? "/en/" : "/";
const label = t(locale, "lang.switch");
const ariaLabel = t(locale, "lang.switchLabel");
---

<a href={targetHref} class="lang-switch" aria-label={ariaLabel} data-lang-switch>
{label}
</a>

<script>
function initLangSwitch() {
document.querySelectorAll('[data-lang-switch]').forEach(link => {
link.addEventListener('click', () => {
try {
const href = (link as HTMLAnchorElement).href;
const isEnglish = href.includes('/en');
localStorage.setItem('preferred-locale', isEnglish ? 'en' : 'es');
} catch { /* Private browsing or storage unavailable */ }
});
});
}

document.addEventListener('astro:page-load', initLangSwitch);
initLangSwitch();
</script>

<style>
.lang-switch {
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--tertiary-text);
transition: color 0.2s ease-in-out;
}

.lang-switch:hover {
color: var(--accent);
}
</style>
Loading