diff --git a/data/cv-en.json b/data/cv-en.json index f81e79a..9b9a74e 100644 --- a/data/cv-en.json +++ b/data/cv-en.json @@ -309,27 +309,6 @@ ], "skills": { "technical": [ - { - "category": "AI-Assisted Development", - "proficiency": 5, - "items": [ - "AI Development Workflows (Claude Code, Copilot, GPT-4)", - "Agent-Based & Spec-Driven Development", - "Prompt Engineering & AI Integration", - "Automated Code Generation & Documentation", - "OpenAI & Anthropic APIs" - ] - }, - { - "category": "SAP Technologies", - "proficiency": 5, - "items": [ - "SAP Customer Data Cloud (CDC)", - "SAP Cloud Platform", - "SAP S/4HANA", - "GDPR Compliance & Data Protection" - ] - }, { "category": "Programming Languages", "proficiency": 4, @@ -347,6 +326,27 @@ "Assembler" ] }, + { + "category": "SAP Technologies", + "proficiency": 5, + "items": [ + "SAP Customer Data Cloud (CDC)", + "SAP Cloud Platform", + "SAP S/4HANA", + "GDPR Compliance & Data Protection" + ] + }, + { + "category": "AI-Assisted Development", + "proficiency": 5, + "items": [ + "AI Development Workflows (Claude Code, Copilot, GPT-4)", + "Agent-Based & Spec-Driven Development", + "Prompt Engineering & AI Integration", + "Automated Code Generation & Documentation", + "OpenAI & Anthropic APIs" + ] + }, { "category": "Go Ecosystem", "proficiency": 5, @@ -414,20 +414,6 @@ "Enterprise Application Servers (Tomcat, JBoss, WebLogic)" ] }, - { - "category": "Databases", - "proficiency": 4, - "sidebar": "right", - "items": [ - "PostgreSQL", - "MySQL", - "SQLite", - "Oracle", - "MongoDB (NoSQL)", - "SQL Knowledge", - "Database Design & Optimization" - ] - }, { "category": "Infrastructure & Servers", "proficiency": 5, @@ -452,6 +438,20 @@ "Process Automation & Scripting" ] }, + { + "category": "Databases", + "proficiency": 4, + "sidebar": "right", + "items": [ + "PostgreSQL", + "MySQL", + "SQLite", + "Oracle", + "MongoDB (NoSQL)", + "SQL Knowledge", + "Database Design & Optimization" + ] + }, { "category": "Team Management", "proficiency": 4, @@ -763,8 +763,8 @@ "title": "La Porra.club - Football Prediction Platform", "url": "https://laporra.club", "projectLogo": "laporra.png", + "gitRepoUrl": "/Users/txeo/laporra", "location": "Online", - "startDate": "2024-06", "current": true, "technologies": ["Node.js", "Hono", "HTMX", "Panini Templates", "Server-Side Rendering"], "shortDescription": "Private invitation-only platform for friends to predict football competition results. Features gamification with digital rewards and competitive scoring system.", @@ -799,14 +799,15 @@ "projectLogo": "", "location": "Various", "startDate": "2015", + "endDate": "2016", "current": false, "technologies": ["JavaScript", "React", "Node.js", "PHP", "WordPress", "Web Development"], "shortDescription": "Collection of client projects and websites including Lidering, Jorpack, Delivery Bikes BCN, and Mobbeel where I contributed to development, implementation, and technical solutions across various industries.", "responsibilities": [ - "Lidering
Lidering (via Twentic): Real estate and property management platform development
", - "Jorpack
Jorpack (via Twentic): Industrial packaging solutions and corporate website
", - "Delivery Bikes BCN
Delivery Bikes BCN: Bicycle delivery service platform for Barcelona
", - "
Mobbeel: Biometric authentication and identity verification solutions website
" + "Lidering
Lidering (via Twentic) 2015: Developed and implemented comprehensive real estate and property management platform with advanced search functionality, property listings, and client management features
", + "Jorpack
Jorpack (via Twentic) 2015: Created corporate website and e-commerce solution for industrial packaging company, featuring product catalog, custom quote system, and business process integration
", + "Delivery Bikes BCN
Delivery Bikes BCN 2016: Built web platform for bicycle delivery service in Barcelona, including route optimization, real-time tracking, and customer booking system
", + "
Mobbeel 2015: Designed and developed corporate website for biometric authentication and identity verification solutions provider, showcasing security products and enterprise services
" ] } ], @@ -862,11 +863,11 @@ } ], "other": { - "driverLicense": "Type B" + "driverLicense": "Type B" }, "meta": { - "version": "2024", - "lastUpdated": "2024-10-18", + "version": "2025-11-09", + "lastUpdated": "2025-11-08", "format": "JSON Resume Extended", "language": "en" } diff --git a/data/cv-es.json b/data/cv-es.json index 662a0e8..5b18dca 100644 --- a/data/cv-es.json +++ b/data/cv-es.json @@ -309,27 +309,6 @@ ], "skills": { "technical": [ - { - "category": "Desarrollo Asistido por IA", - "proficiency": 5, - "items": [ - "Flujos de Desarrollo con IA (Claude Code, Copilot, GPT-4)", - "Desarrollo Basado en Agentes y Especificaciones", - "Ingeniería de Prompts e Integración de IA", - "Generación Automática de Código y Documentación", - "APIs OpenAI y Anthropic" - ] - }, - { - "category": "Tecnologías SAP", - "proficiency": 5, - "items": [ - "SAP Customer Data Cloud (CDC)", - "SAP Cloud Platform", - "SAP S/4HANA", - "Cumplimiento GDPR y Protección de Datos" - ] - }, { "category": "Lenguajes de Programación", "proficiency": 4, @@ -347,6 +326,27 @@ "Assembler" ] }, + { + "category": "Tecnologías SAP", + "proficiency": 5, + "items": [ + "SAP Customer Data Cloud (CDC)", + "SAP Cloud Platform", + "SAP S/4HANA", + "Cumplimiento GDPR y Protección de Datos" + ] + }, + { + "category": "Desarrollo Asistido por IA", + "proficiency": 5, + "items": [ + "Flujos de Desarrollo con IA (Claude Code, Copilot, GPT-4)", + "Desarrollo Basado en Agentes y Especificaciones", + "Ingeniería de Prompts e Integración de IA", + "Generación Automática de Código y Documentación", + "APIs OpenAI y Anthropic" + ] + }, { "category": "Ecosistema Go", "proficiency": 5, @@ -414,20 +414,6 @@ "Servidores de Aplicaciones Enterprise (Tomcat, JBoss, WebLogic)" ] }, - { - "category": "Bases de Datos", - "proficiency": 4, - "sidebar": "right", - "items": [ - "PostgreSQL", - "MySQL", - "SQLite", - "Oracle", - "MongoDB (NoSQL)", - "Dominio de SQL", - "Diseño y Optimización de Bases de Datos" - ] - }, { "category": "Infraestructura y Servidores", "proficiency": 5, @@ -452,6 +438,20 @@ "Automatización de Procesos y Scripting" ] }, + { + "category": "Bases de Datos", + "proficiency": 4, + "sidebar": "right", + "items": [ + "PostgreSQL", + "MySQL", + "SQLite", + "Oracle", + "MongoDB (NoSQL)", + "Dominio de SQL", + "Diseño y Optimización de Bases de Datos" + ] + }, { "category": "Gestión de Equipos", "proficiency": 4, @@ -768,8 +768,8 @@ "title": "La Porra.club - Plataforma de Predicción de Fútbol", "url": "https://laporra.club", "projectLogo": "laporra.png", + "gitRepoUrl": "/Users/txeo/laporra", "location": "Online", - "startDate": "2024-06", "current": true, "technologies": ["Node.js", "Hono", "HTMX", "Plantillas Panini", "Renderizado del Lado del Servidor"], "shortDescription": "Plataforma privada de acceso por invitación para amigos para predecir resultados de competiciones de fútbol. Incluye gamificación con recompensas digitales y sistema de puntuación competitivo.", @@ -804,14 +804,15 @@ "projectLogo": "", "location": "Varios", "startDate": "2015", + "endDate": "2016", "current": false, "technologies": ["JavaScript", "React", "Node.js", "PHP", "WordPress", "Desarrollo Web"], "shortDescription": "Colección de proyectos de clientes y sitios web incluyendo Lidering, Jorpack, Delivery Bikes BCN y Mobbeel donde contribuí al desarrollo, implementación y soluciones técnicas en diversas industrias.", "responsibilities": [ - "Lidering
Lidering (a través de Twentic): Desarrollo de plataforma de gestión inmobiliaria y propiedades
", - "Jorpack
Jorpack (a través de Twentic): Soluciones de embalaje industrial y sitio web corporativo
", - "Delivery Bikes BCN
Delivery Bikes BCN: Plataforma de servicio de entrega en bicicleta para Barcelona
", - "
Mobbeel: Sitio web de soluciones de autenticación biométrica y verificación de identidad
" + "Lidering
Lidering (a través de Twentic) 2015: Desarrollé e implementé plataforma integral de gestión inmobiliaria y propiedades con funcionalidad avanzada de búsqueda, listado de propiedades y gestión de clientes
", + "Jorpack
Jorpack (a través de Twentic) 2015: Creé sitio web corporativo y solución e-commerce para empresa de embalaje industrial, con catálogo de productos, sistema de presupuestos personalizados e integración de procesos de negocio
", + "Delivery Bikes BCN
Delivery Bikes BCN 2016: Construí plataforma web para servicio de entrega en bicicleta en Barcelona, incluyendo optimización de rutas, seguimiento en tiempo real y sistema de reservas para clientes
", + "
Mobbeel 2015: Diseñé y desarrollé sitio web corporativo para proveedor de soluciones de autenticación biométrica y verificación de identidad, mostrando productos de seguridad y servicios empresariales
" ] } ], @@ -867,11 +868,11 @@ } ], "other": { - "driverLicense": "Tipo B" + "driverLicense": "Tipo B" }, "meta": { - "version": "2024", - "lastUpdated": "2024-10-18", + "version": "2025-11-09", + "lastUpdated": "2025-11-08", "format": "JSON Resume Extended", "language": "es" } diff --git a/internal/handlers/cv.go b/internal/handlers/cv.go index 1a3eae2..f2591c4 100644 --- a/internal/handlers/cv.go +++ b/internal/handlers/cv.go @@ -4,6 +4,9 @@ import ( "fmt" "log" "net/http" + "os" + "os/exec" + "strings" "time" "github.com/juanatsap/cv-site/internal/models" @@ -58,6 +61,11 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) { ) } + // Process projects for dynamic dates + for i := range cv.Projects { + processProjectDates(&cv.Projects[i], lang) + } + // Split skills between left and right sidebars skillsLeft, skillsRight := splitSkills(cv.Skills.Technical) @@ -122,6 +130,11 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) { ) } + // Process projects for dynamic dates + for i := range cv.Projects { + processProjectDates(&cv.Projects[i], lang) + } + // Split skills between left and right sidebars skillsLeft, skillsRight := splitSkills(cv.Skills.Technical) @@ -325,3 +338,77 @@ func calculateDuration(startDate, endDate string, current bool, lang string) str return result } + +// processProjectDates calculates dynamic dates for projects +// If a project has a gitRepoUrl, it fetches the first commit date +// For current projects, it sets the current system date +func processProjectDates(project *models.Project, lang string) { + now := time.Now() + + // Set dynamic current date for ongoing projects + if project.Current { + if lang == "es" { + project.DynamicDate = "Presente" + } else { + project.DynamicDate = "Present" + } + } + + // If project has a git repository URL, fetch the first commit date + if project.GitRepoUrl != "" { + commitDate := getGitRepoFirstCommitDate(project.GitRepoUrl) + if commitDate != "" { + project.ComputedStartDate = commitDate + } + } + + // If no computed date and no static date, use current date for current projects + if project.ComputedStartDate == "" && project.StartDate == "" && project.Current { + project.ComputedStartDate = now.Format("2006-01") + } + + // If we have a computed date but no static date, use the computed one + if project.ComputedStartDate != "" && project.StartDate == "" { + project.StartDate = project.ComputedStartDate + } +} + +// getGitRepoFirstCommitDate fetches the first commit date from a git repository +// Supports local git repository paths +func getGitRepoFirstCommitDate(repoPath string) string { + // Check if the path exists and is a directory + info, err := os.Stat(repoPath) + if err != nil || !info.IsDir() { + return "" + } + + // Execute git command to get the first commit date + // Format: YYYY-MM (to match StartDate format) + cmd := exec.Command("git", "-C", repoPath, "log", "--reverse", "--format=%ci", "--date=format:%Y-%m") + cmd.Dir = repoPath + + output, err := cmd.Output() + if err != nil { + return "" + } + + // Parse the output to get the first commit date + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + if len(lines) == 0 { + return "" + } + + // Extract YYYY-MM from the first commit timestamp + // Format of output: "2024-06-15 10:30:45 +0200" + firstLine := lines[0] + parts := strings.Fields(firstLine) + if len(parts) > 0 { + datePart := parts[0] // "2024-06-15" + dateParts := strings.Split(datePart, "-") + if len(dateParts) >= 2 { + return dateParts[0] + "-" + dateParts[1] // "2024-06" + } + } + + return "" +} diff --git a/internal/models/cv.go b/internal/models/cv.go index f465d0b..e945e74 100644 --- a/internal/models/cv.go +++ b/internal/models/cv.go @@ -102,14 +102,19 @@ type Language struct { type Project struct { Title string `json:"title"` URL string `json:"url"` - ProjectLogo string `json:"projectLogo,omitempty"` // Optional logo filename + ProjectLogo string `json:"projectLogo,omitempty"` // Optional logo filename + GitRepoUrl string `json:"gitRepoUrl,omitempty"` // Optional git repository URL for dynamic dates Location string `json:"location"` - StartDate string `json:"startDate"` + StartDate string `json:"startDate,omitempty"` // Optional static start date Current bool `json:"current"` MaintainedBy string `json:"maintainedBy,omitempty"` // Optional maintainer name (e.g., "SAP") Technologies []string `json:"technologies"` ShortDescription string `json:"shortDescription"` Responsibilities []string `json:"responsibilities"` + + // Computed fields (not stored in JSON) + ComputedStartDate string `json:"-"` // Dynamically calculated from git repo or system + DynamicDate string `json:"-"` // Current date for ongoing projects } type Award struct { diff --git a/static/css/main.css b/static/css/main.css index 6bc4c5a..73bb25b 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -850,10 +850,11 @@ iconify-icon { } .language-item { - font-size: 0.9rem; + font-size: 1.1rem!important; color: var(--text-dark); - margin-bottom: 0.3rem; - line-height: 1.4; + margin-bottom: 0.3rem!important; + line-height: 1.4!important; + margin-left: 2rem!important; } .language-item small { @@ -1073,12 +1074,14 @@ iconify-icon { /* References */ .reference-item { - margin-bottom: 0.6rem; - line-height: 1.4; + margin-bottom: 0!important; + line-height: 1.4!important; + margin-left: 2rem!important; + font-size: 1.1rem!important; } .reference-item a { - font-size: 0.9em; + color: var(--accent-blue); text-decoration: none; word-break: break-word; @@ -1509,7 +1512,10 @@ a:focus { .language-item, .reference-item, .other-content { - margin-bottom: 1em; + margin-bottom: 0!important; + line-height: 1.4!important; + margin-left: 2rem!important; + font-size: 1.1rem!important; } /* Award item with logo */ @@ -1526,11 +1532,68 @@ a:focus { /* Keep border on all award items including last one */ +/* ======================================== + HIDE LOGOS, ICONS, AND BADGES MODE + ======================================== */ + /* Adjust gap when logos are hidden */ .cv-paper:not(.show-logos) .award-item { gap: 0; } +/* Hide all logos when .show-logos is not present */ +.cv-paper:not(.show-logos) .company-logo, +.cv-paper:not(.show-logos) .award-logo, +.cv-paper:not(.show-logos) .project-icon, +.cv-paper:not(.show-logos) .course-icon { + display: none !important; +} + +/* Hide logos inside responsibilities (Drolosoft sub-clients, Third Party projects) */ +.cv-paper:not(.show-logos) .responsibilities li img, +.cv-paper:not(.show-logos) .responsibilities li iconify-icon.default-company-icon { + display: none !important; +} + +/* Adjust layout for responsibilities without logos */ +.cv-paper:not(.show-logos) .responsibilities li:has(img), +.cv-paper:not(.show-logos) .responsibilities li:has(iconify-icon) { + display: block !important; + grid-template-columns: none !important; + padding-left: 1.2rem !important; +} + +/* Restore bullet points for responsibilities without logos */ +.cv-paper:not(.show-logos) .responsibilities li:has(img):before, +.cv-paper:not(.show-logos) .responsibilities li:has(iconify-icon):before { + display: block !important; +} + +/* Hide all section icons */ +.cv-paper:not(.show-logos) .section-icon { + display: none !important; +} + +/* Hide all badges (Current, Expired, Maintained By) */ +.cv-paper:not(.show-logos) .current-badge, +.cv-paper:not(.show-logos) .expired-badge, +.cv-paper:not(.show-logos) .maintained-badge { + display: none !important; +} + +/* Adjust experience items layout when logos are hidden */ +.cv-paper:not(.show-logos) .experience-item { + display: block !important; +} + +/* Adjust project and course items layout when icons are hidden */ +.cv-paper:not(.show-logos) .project-item, +.cv-paper:not(.show-logos) .course-item, +.cv-paper:not(.show-logos) .award-item { + display: block !important; + gap: 0 !important; +} + .award-logo { flex-shrink: 0; display: block; diff --git a/templates/cv-content.html b/templates/cv-content.html index c971b4a..98546e0 100644 --- a/templates/cv-content.html +++ b/templates/cv-content.html @@ -197,7 +197,7 @@

- {{if eq .Lang "es"}}Proyectos Personales{{else}}Personal Projects{{end}} + {{if eq .Lang "es"}}Proyectos Personales / Freelance{{else}}Personal / Freelance Projects{{end}}

{{range .CV.Projects}}
@@ -227,7 +227,16 @@ {{if eq $.Lang "es"}}MANTENIDO POR{{else}}MAINTAINED BY{{end}} {{.MaintainedBy}} {{end}} - {{.StartDate}} {{if .Current}}/ {{if eq $.Lang "es"}}presente{{else}}now{{end}}{{end}} + + {{if .StartDate}}{{.StartDate}}{{end}} + {{if .Current}} + {{if .DynamicDate}} + / {{.DynamicDate}} + {{else}} + / {{if eq $.Lang "es"}}presente{{else}}now{{end}} + {{end}} + {{end}} +  -  ({{.Location}})
diff --git a/templates/index.html b/templates/index.html index d45a3fe..4e9e834 100644 --- a/templates/index.html +++ b/templates/index.html @@ -205,6 +205,10 @@ {{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}} + + + {{if eq .Lang "es"}}Proyectos Personales / Freelance{{else}}Personal / Freelance Projects{{end}} + {{if eq .Lang "es"}}Cursos Realizados{{else}}Courses{{end}}