// 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) => `

Experiencia ${index + 1}

${exp.highlights.map((h, i) => `
`).join('')}
`).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 ? ` ` : ''}
`).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 ? ` ` : ''}
`).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 ? ` ` : ''}
`).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(); } });