// State management
let cvData = {
personal: {
name: '',
email: '',
phone: '',
website: '',
linkedin: '',
github: '',
location: '',
headline: ''
},
summary: {
title: 'RESUMEN PROFESIONAL',
content: ''
},
experience: [],
education: [],
projects: [],
skills: [],
languages: []
};
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadFromLocalStorage();
setupEventListeners();
updatePreview();
updateChecklist();
});
// Event Listeners
function setupEventListeners() {
// Personal info
['name', 'email', 'phone', 'website', 'linkedin', 'github', 'location', 'headline'].forEach(field => {
const element = document.getElementById(`input-${field}`);
if (element) {
element.addEventListener('input', (e) => {
cvData.personal[field] = e.target.value;
updatePreview();
updateChecklist();
saveToLocalStorage();
});
}
});
// Summary
const summaryTitle = document.getElementById('input-summary-title');
const summaryContent = document.getElementById('input-summary-content');
if (summaryTitle) {
summaryTitle.addEventListener('input', (e) => {
cvData.summary.title = e.target.value;
updatePreview();
updateChecklist();
saveToLocalStorage();
});
}
if (summaryContent) {
summaryContent.addEventListener('input', (e) => {
cvData.summary.content = e.target.value;
updatePreview();
updateChecklist();
saveToLocalStorage();
});
}
}
// Add functions
function addExperience() {
const id = Date.now();
cvData.experience.push({
id,
company: '',
position: '',
stack: '',
location: '',
startDate: '',
endDate: '',
highlights: ['']
});
renderExperience();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function addEducation() {
const id = Date.now();
cvData.education.push({
id,
institution: '',
degree: '',
location: '',
startDate: '',
endDate: '',
highlights: ['']
});
renderEducation();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function addProject() {
const id = Date.now();
cvData.projects.push({
id,
name: '',
date: '',
description: '',
location: '',
startDate: '',
endDate: '',
highlights: ['']
});
renderProjects();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function addSkill() {
const id = Date.now();
cvData.skills.push({
id,
label: '',
details: ''
});
renderSkills();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function addLanguage() {
const id = Date.now();
cvData.languages.push({
id,
label: '',
details: ''
});
renderLanguages();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
// Render functions
function renderExperience() {
const container = document.getElementById('experience-container');
container.innerHTML = cvData.experience.map((exp, index) => `
`).join('');
lucide.createIcons();
}
function renderEducation() {
const container = document.getElementById('education-container');
container.innerHTML = cvData.education.map((edu, index) => `
Educación ${index + 1}
${edu.highlights.map((h, i) => `
•
`).join('')}
`).join('');
lucide.createIcons();
}
function renderProjects() {
const container = document.getElementById('project-container');
container.innerHTML = cvData.projects.map((proj, index) => `
Proyecto/Certificación ${index + 1}
${proj.highlights.map((h, i) => `
•
`).join('')}
`).join('');
lucide.createIcons();
}
function renderSkills() {
const container = document.getElementById('skills-container');
container.innerHTML = cvData.skills.map((skill, index) => `
`).join('');
lucide.createIcons();
}
function renderLanguages() {
const container = document.getElementById('languages-container');
container.innerHTML = cvData.languages.map((lang, index) => `
`).join('');
lucide.createIcons();
}
// Update functions
function updateExperience(id, field, value) {
const exp = cvData.experience.find(e => e.id === id);
if (exp) {
exp[field] = value;
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
function updateEducation(id, field, value) {
const edu = cvData.education.find(e => e.id === id);
if (edu) {
edu[field] = value;
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
function updateProject(id, field, value) {
const proj = cvData.projects.find(p => p.id === id);
if (proj) {
proj[field] = value;
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
function updateSkill(id, field, value) {
const skill = cvData.skills.find(s => s.id === id);
if (skill) {
skill[field] = value;
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
function updateLanguage(id, field, value) {
const lang = cvData.languages.find(l => l.id === id);
if (lang) {
lang[field] = value;
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
function updateHighlight(section, id, index, value) {
const item = cvData[section].find(item => item.id === id);
if (item && item.highlights) {
item.highlights[index] = value;
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
function addHighlight(section, id) {
const item = cvData[section].find(item => item.id === id);
if (item) {
item.highlights.push('');
if (section === 'experience') renderExperience();
else if (section === 'education') renderEducation();
else if (section === 'projects') renderProjects();
}
}
function removeHighlight(section, id, index) {
const item = cvData[section].find(item => item.id === id);
if (item && item.highlights) {
item.highlights.splice(index, 1);
if (section === 'experience') renderExperience();
else if (section === 'education') renderEducation();
else if (section === 'projects') renderProjects();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
}
// Remove functions
function removeExperience(id) {
cvData.experience = cvData.experience.filter(e => e.id !== id);
renderExperience();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function removeEducation(id) {
cvData.education = cvData.education.filter(e => e.id !== id);
renderEducation();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function removeProject(id) {
cvData.projects = cvData.projects.filter(p => p.id !== id);
renderProjects();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function removeSkill(id) {
cvData.skills = cvData.skills.filter(s => s.id !== id);
renderSkills();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
function removeLanguage(id) {
cvData.languages = cvData.languages.filter(l => l.id !== id);
renderLanguages();
updatePreview();
updateChecklist();
saveToLocalStorage();
}
// Preview generation
function updatePreview() {
// Personal info
document.getElementById('preview-name').textContent = cvData.personal.name || 'TU NOMBRE';
const contactParts = [];
if (cvData.personal.location) contactParts.push(cvData.personal.location);
if (cvData.personal.phone) contactParts.push(cvData.personal.phone);
if (cvData.personal.email) contactParts.push(cvData.personal.email);
if (cvData.personal.linkedin) contactParts.push(`linkedin.com/in/${cvData.personal.linkedin}`);
if (cvData.personal.github) contactParts.push(`github.com/${cvData.personal.github}`);
if (cvData.personal.website) contactParts.push(cvData.personal.website);
document.getElementById('preview-contact').innerHTML = contactParts.join(' | ');
// Summary
const summarySection = document.getElementById('preview-summary-section');
if (cvData.summary.content) {
summarySection.style.display = 'block';
document.getElementById('preview-summary-title').textContent = cvData.summary.title || 'RESUMEN PROFESIONAL';
document.getElementById('preview-summary-content').textContent = cvData.summary.content;
} else {
summarySection.style.display = 'none';
}
// Experience
const expSection = document.getElementById('preview-experience-section');
const expContainer = document.getElementById('preview-experience');
if (cvData.experience.length > 0 && cvData.experience.some(e => e.company || e.position)) {
expSection.style.display = 'block';
expContainer.innerHTML = cvData.experience.map(exp => `
${exp.company}
${exp.stack ? ` | ${exp.stack}` : ''}
${exp.startDate}${exp.endDate ? ` - ${exp.endDate}` : ''}
${exp.position}${exp.location ? ` | ${exp.location}` : ''}
${exp.highlights.filter(h => h).length > 0 ? `
${exp.highlights.filter(h => h).map(h => `- ${h}
`).join('')}
` : ''}
`).join('');
} else {
expSection.style.display = 'none';
}
// Education
const eduSection = document.getElementById('preview-education-section');
const eduContainer = document.getElementById('preview-education');
if (cvData.education.length > 0 && cvData.education.some(e => e.institution || e.degree)) {
eduSection.style.display = 'block';
eduContainer.innerHTML = cvData.education.map(edu => `
${edu.institution}
${edu.startDate}${edu.endDate ? ` - ${edu.endDate}` : ''}
${edu.degree}${edu.location ? ` | ${edu.location}` : ''}
${edu.highlights.filter(h => h).length > 0 ? `
${edu.highlights.filter(h => h).map(h => `- ${h}
`).join('')}
` : ''}
`).join('');
} else {
eduSection.style.display = 'none';
}
// Projects
const projSection = document.getElementById('preview-project-section');
const projContainer = document.getElementById('preview-projects');
if (cvData.projects.length > 0 && cvData.projects.some(p => p.name)) {
projSection.style.display = 'block';
projContainer.innerHTML = cvData.projects.map(proj => `
${proj.name}
${proj.date || (proj.startDate + (proj.endDate ? ` - ${proj.endDate}` : ''))}
${proj.description ? `
${proj.description}
` : ''}
${proj.location ? `
${proj.location}
` : ''}
${proj.highlights.filter(h => h).length > 0 ? `
${proj.highlights.filter(h => h).map(h => `- ${h}
`).join('')}
` : ''}
`).join('');
} else {
projSection.style.display = 'none';
}
// Skills
const skillSection = document.getElementById('preview-skills-section');
if (cvData.skills.length > 0 && cvData.skills.some(s => s.label)) {
skillSection.style.display = 'block';
document.getElementById('preview-skills').innerHTML = cvData.skills
.filter(s => s.label)
.map(s => `${s.label}${s.details ? ` (${s.details})` : ''}`)
.join(' • ');
} else {
skillSection.style.display = 'none';
}
// Languages
const langSection = document.getElementById('preview-languages-section');
if (cvData.languages.length > 0 && cvData.languages.some(l => l.label)) {
langSection.style.display = 'block';
document.getElementById('preview-languages').innerHTML = cvData.languages
.filter(l => l.label)
.map(l => `${l.label}${l.details ? ` (${l.details})` : ''}`)
.join(' • ');
} else {
langSection.style.display = 'none';
}
}
// Checklist and ATS compliance
function updateChecklist() {
let score = 0;
let maxScore = 6;
// Check personal
const hasPersonal = cvData.personal.name && cvData.personal.email && cvData.personal.phone && cvData.personal.location;
updateCheckStatus('check-personal', hasPersonal);
if (hasPersonal) score++;
// Check summary
const hasSummary = cvData.summary.content.length > 50;
updateCheckStatus('check-summary', hasSummary);
if (hasSummary) score++;
// Check experience
const hasExperience = cvData.experience.length > 0 && cvData.experience.some(e => e.company && e.position);
updateCheckStatus('check-experience', hasExperience);
if (hasExperience) score++;
// Check education
const hasEducation = cvData.education.length > 0 && cvData.education.some(e => e.institution && e.degree);
updateCheckStatus('check-education', hasEducation);
if (hasEducation) score++;
// Check skills
const hasSkills = cvData.skills.length > 0 && cvData.skills.some(s => s.label);
updateCheckStatus('check-skills', hasSkills);
if (hasSkills) score++;
// ATS specific check
const atsIssues = checkATSIssues();
const atsGood = atsIssues.length === 0;
updateCheckStatus('check-ats', atsGood, atsGood ? 'Optimización ATS' : `Problemas ATS: ${atsIssues.length}`);
if (atsGood) score++;
// Update progress
const percentage = Math.round((score / maxScore) * 100);
document.getElementById('progress-percent').textContent = `${percentage}%`;
document.getElementById('progress-bar').style.width = `${percentage}%`;
// Update ATS tips
const tipsContainer = document.getElementById('ats-tips');
if (atsIssues.length > 0) {
tipsContainer.innerHTML = atsIssues.slice(0, 3).map(issue => `• ${issue}`).join('');
} else {
tipsContainer.innerHTML = '✓ Formato compatible con ATS';
}
}
function updateCheckStatus(id, completed, customText = null) {
const element = document.getElementById(id);
if (!element) return;
const icon = element.querySelector('.check-icon');
const text = element.querySelector('.check-text');
if (completed) {
icon.classList.remove('text-gray-300');
icon.classList.add('text-green-500');
icon.innerHTML = '';
element.classList.add('opacity-100');
element.classList.remove('opacity-60');
} else {
icon.classList.add('text-gray-300');
icon.classList.remove('text-green-500');
icon.innerHTML = '';
element.classList.remove('opacity-100');
element.classList.add('opacity-60');
}
if (customText) {
text.textContent = customText;
}
}
function checkATSIssues() {
const issues = [];
// Check for photos (none in our app, but good to note)
// Check for tables (we don't use them)
// Check section headers
const standardHeaders = ['EXPERIENCIA', 'EDUCACIÓN', 'HABILIDADES', 'RESUMEN'];
// Check contact info format
if (cvData.personal.email && !cvData.personal.email.includes('@')) {
issues.push('Formato de email inválido');
}
// Check for keywords in experience
if (cvData.experience.length > 0) {
const hasMetrics = cvData.experience.some(exp =>
exp.highlights.some(h => /\d+/.test(h))
);
if (!hasMetrics) {
issues.push('Añade métricas cuantificables (%, números) a tu experiencia');
}
}
// Check summary length
if (cvData.summary.content && cvData.summary.content.length < 50) {
issues.push('El resumen es muy corto (mínimo 50 caracteres recomendados)');
}
// Check for skills variety
if (cvData.skills.length < 3) {
issues.push('Añade más habilidades técnicas relevantes');
}
return issues;
}
// View switching
function switchView(view) {
const editorView = document.getElementById('editor-view');
const previewView = document.getElementById('preview-view');
const btnEditor = document.getElementById('btn-editor');
const btnPreview = document.getElementById('btn-preview');
if (view === 'editor') {
editorView.classList.remove('hidden');
previewView.classList.add('hidden');
btnEditor.classList.add('bg-indigo-600', 'text-white');
btnEditor.classList.remove('text-gray-600', 'hover:bg-gray-100');
btnPreview.classList.remove('bg-indigo-600', 'text-white');
btnPreview.classList.add('text-gray-600', 'hover:bg-gray-100');
} else {
editorView.classList.add('hidden');
previewView.classList.remove('hidden');
btnPreview.classList.add('bg-indigo-600', 'text-white');
btnPreview.classList.remove('text-gray-600', 'hover:bg-gray-100');
btnEditor.classList.remove('bg-indigo-600', 'text-white');
btnEditor.classList.add('text-gray-600', 'hover:bg-gray-100');
updatePreview();
}
}
function toggleMobileMenu() {
// Simple mobile menu toggle if needed
const sidebar = document.querySelector('aside');
sidebar.classList.toggle('hidden');
}
// Storage
function saveToLocalStorage() {
localStorage.setItem('cvData', JSON.stringify(cvData));
}
function loadFromLocalStorage() {
const saved = localStorage.getItem('cvData');
if (saved) {
cvData = JSON.parse(saved);
// Load personal info
Object.keys(cvData.personal).forEach(key => {
const el = document.getElementById(`input-${key}`);
if (el) el.value = cvData.personal[key];
});
// Load summary
const sumTitle = document.getElementById('input-summary-title');
const sumContent = document.getElementById('input-summary-content');
if (sumTitle) sumTitle.value = cvData.summary.title;
if (sumContent) sumContent.value = cvData.summary.content;
// Render all sections
renderExperience();
renderEducation();
renderProjects();
renderSkills();
renderLanguages();
}
}
// Initialize checklist HTML
document.addEventListener('DOMContentLoaded', () => {
const checklist = document.getElementById('checklist');
if (checklist) {
checklist.innerHTML = `
Información Personal
Resumen Profesional
Experiencia Laboral
Educación
Habilidades
Optimización ATS
`;
lucide.createIcons();
}
});