dazeimon's picture
Initial DeepSite commit
8579f38 verified
Raw
History Blame Contribute Delete
32.7 kB
// 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) => `
<div class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex justify-between items-start mb-3">
<h3 class="font-semibold text-gray-700">Experiencia ${index + 1}</h3>
<button onclick="removeExperience(${exp.id})" class="text-red-500 hover:text-red-700">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
<input type="text" placeholder="Empresa *" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${exp.company}" onchange="updateExperience(${exp.id}, 'company', this.value)">
<input type="text" placeholder="Cargo *" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${exp.position}" onchange="updateExperience(${exp.id}, 'position', this.value)">
<input type="text" placeholder="Stack tecnológico" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${exp.stack}" onchange="updateExperience(${exp.id}, 'stack', this.value)">
<input type="text" placeholder="Ubicación" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${exp.location}" onchange="updateExperience(${exp.id}, 'location', this.value)">
<input type="text" placeholder="Fecha inicio (MM/AAAA)" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${exp.startDate}" onchange="updateExperience(${exp.id}, 'startDate', this.value)">
<input type="text" placeholder="Fecha fin (MM/AAAA o Presente)" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${exp.endDate}" onchange="updateExperience(${exp.id}, 'endDate', this.value)">
</div>
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700">Logros (bullets)</label>
${exp.highlights.map((h, i) => `
<div class="flex gap-2">
<span class="text-gray-500 mt-2">•</span>
<input type="text" class="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
value="${h}" onchange="updateHighlight('experience', ${exp.id}, ${i}, this.value)" placeholder="Describe un logro cuantificable...">
<button onclick="removeHighlight('experience', ${exp.id}, ${i})" class="text-red-500 hover:text-red-700 px-2">×</button>
</div>
`).join('')}
<button onclick="addHighlight('experience', ${exp.id})" class="text-sm text-indigo-600 hover:text-indigo-800 font-medium mt-2">
+ Añadir logro
</button>
</div>
</div>
`).join('');
lucide.createIcons();
}
function renderEducation() {
const container = document.getElementById('education-container');
container.innerHTML = cvData.education.map((edu, index) => `
<div class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex justify-between items-start mb-3">
<h3 class="font-semibold text-gray-700">Educación ${index + 1}</h3>
<button onclick="removeEducation(${edu.id})" class="text-red-500 hover:text-red-700">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
<input type="text" placeholder="Institución *" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${edu.institution}" onchange="updateEducation(${edu.id}, 'institution', this.value)">
<input type="text" placeholder="Carrera/Título *" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${edu.degree}" onchange="updateEducation(${edu.id}, 'degree', this.value)">
<input type="text" placeholder="Ubicación" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${edu.location}" onchange="updateEducation(${edu.id}, 'location', this.value)">
<div class="grid grid-cols-2 gap-2">
<input type="text" placeholder="Inicio" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${edu.startDate}" onchange="updateEducation(${edu.id}, 'startDate', this.value)">
<input type="text" placeholder="Fin" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${edu.endDate}" onchange="updateEducation(${edu.id}, 'endDate', this.value)">
</div>
</div>
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700">Detalres relevantes</label>
${edu.highlights.map((h, i) => `
<div class="flex gap-2">
<span class="text-gray-500 mt-2">•</span>
<input type="text" class="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
value="${h}" onchange="updateHighlight('education', ${edu.id}, ${i}, this.value)">
<button onclick="removeHighlight('education', ${edu.id}, ${i})" class="text-red-500 hover:text-red-700 px-2">×</button>
</div>
`).join('')}
<button onclick="addHighlight('education', ${edu.id})" class="text-sm text-indigo-600 hover:text-indigo-800 font-medium mt-2">
+ Añadir detalle
</button>
</div>
</div>
`).join('');
lucide.createIcons();
}
function renderProjects() {
const container = document.getElementById('project-container');
container.innerHTML = cvData.projects.map((proj, index) => `
<div class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex justify-between items-start mb-3">
<h3 class="font-semibold text-gray-700">Proyecto/Certificación ${index + 1}</h3>
<button onclick="removeProject(${proj.id})" class="text-red-500 hover:text-red-700">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
<input type="text" placeholder="Nombre *" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${proj.name}" onchange="updateProject(${proj.id}, 'name', this.value)">
<input type="text" placeholder="Fecha" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${proj.date}" onchange="updateProject(${proj.id}, 'date', this.value)">
<input type="text" placeholder="Ubicación" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${proj.location}" onchange="updateProject(${proj.id}, 'location', this.value)">
<div class="grid grid-cols-2 gap-2">
<input type="text" placeholder="Inicio" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${proj.startDate}" onchange="updateProject(${proj.id}, 'startDate', this.value)">
<input type="text" placeholder="Fin" class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
value="${proj.endDate}" onchange="updateProject(${proj.id}, 'endDate', this.value)">
</div>
</div>
<textarea placeholder="Descripción breve..." class="w-full px-3 py-2 border border-gray-300 rounded text-sm mb-3"
rows="2" onchange="updateProject(${proj.id}, 'description', this.value)">${proj.description}</textarea>
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700">Logros/Detalles</label>
${proj.highlights.map((h, i) => `
<div class="flex gap-2">
<span class="text-gray-500 mt-2">•</span>
<input type="text" class="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
value="${h}" onchange="updateHighlight('projects', ${proj.id}, ${i}, this.value)">
<button onclick="removeHighlight('projects', ${proj.id}, ${i})" class="text-red-500 hover:text-red-700 px-2">×</button>
</div>
`).join('')}
<button onclick="addHighlight('projects', ${proj.id})" class="text-sm text-indigo-600 hover:text-indigo-800 font-medium mt-2">
+ Añadir logro
</button>
</div>
</div>
`).join('');
lucide.createIcons();
}
function renderSkills() {
const container = document.getElementById('skills-container');
container.innerHTML = cvData.skills.map((skill, index) => `
<div class="flex items-center gap-2">
<input type="text" placeholder="Habilidad (ej: JavaScript)" class="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
value="${skill.label}" onchange="updateSkill(${skill.id}, 'label', this.value)">
<input type="text" placeholder="Nivel (ej: Avanzado)" class="w-1/3 px-3 py-2 border border-gray-300 rounded text-sm"
value="${skill.details}" onchange="updateSkill(${skill.id}, 'details', this.value)">
<button onclick="removeSkill(${skill.id})" class="text-red-500 hover:text-red-700 p-2">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
`).join('');
lucide.createIcons();
}
function renderLanguages() {
const container = document.getElementById('languages-container');
container.innerHTML = cvData.languages.map((lang, index) => `
<div class="flex items-center gap-2">
<input type="text" placeholder="Idioma (ej: Inglés)" class="flex-1 px-3 py-2 border border-gray-300 rounded text-sm"
value="${lang.label}" onchange="updateLanguage(${lang.id}, 'label', this.value)">
<input type="text" placeholder="Nivel (ej: C1)" class="w-1/3 px-3 py-2 border border-gray-300 rounded text-sm"
value="${lang.details}" onchange="updateLanguage(${lang.id}, 'details', this.value)">
<button onclick="removeLanguage(${lang.id})" class="text-red-500 hover:text-red-700 p-2">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
`).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 => `
<div class="mb-4">
<div class="flex justify-between items-baseline mb-1">
<div>
<span class="font-bold text-sm">${exp.company}</span>
${exp.stack ? `<span class="text-xs text-gray-600"> | ${exp.stack}</span>` : ''}
</div>
<span class="text-sm italic">${exp.startDate}${exp.endDate ? ` - ${exp.endDate}` : ''}</span>
</div>
<div class="text-sm italic mb-1">${exp.position}${exp.location ? ` | ${exp.location}` : ''}</div>
${exp.highlights.filter(h => h).length > 0 ? `
<ul class="text-sm list-disc ml-5 space-y-0.5">
${exp.highlights.filter(h => h).map(h => `<li>${h}</li>`).join('')}
</ul>
` : ''}
</div>
`).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 => `
<div class="mb-3">
<div class="flex justify-between items-baseline">
<span class="font-bold text-sm">${edu.institution}</span>
<span class="text-sm italic">${edu.startDate}${edu.endDate ? ` - ${edu.endDate}` : ''}</span>
</div>
<div class="text-sm italic">${edu.degree}${edu.location ? ` | ${edu.location}` : ''}</div>
${edu.highlights.filter(h => h).length > 0 ? `
<ul class="text-sm list-disc ml-5 mt-1">
${edu.highlights.filter(h => h).map(h => `<li>${h}</li>`).join('')}
</ul>
` : ''}
</div>
`).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 => `
<div class="mb-3">
<div class="flex justify-between items-baseline">
<span class="font-bold text-sm">${proj.name}</span>
<span class="text-sm italic">${proj.date || (proj.startDate + (proj.endDate ? ` - ${proj.endDate}` : ''))}</span>
</div>
${proj.description ? `<div class="text-sm mb-1">${proj.description}</div>` : ''}
${proj.location ? `<div class="text-sm italic">${proj.location}</div>` : ''}
${proj.highlights.filter(h => h).length > 0 ? `
<ul class="text-sm list-disc ml-5 mt-1">
${proj.highlights.filter(h => h).map(h => `<li>${h}</li>`).join('')}
</ul>
` : ''}
</div>
`).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 => `<span class="mr-4"><strong>${s.label}</strong>${s.details ? ` (${s.details})` : ''}</span>`)
.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 => `<span class="mr-4"><strong>${l.label}</strong>${l.details ? ` (${l.details})` : ''}</span>`)
.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 => `<li>• ${issue}</li>`).join('');
} else {
tipsContainer.innerHTML = '<li class="text-green-700">✓ Formato compatible con ATS</li>';
}
}
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 = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-circle w-5 h-5"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>';
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 = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle w-5 h-5"><circle cx="12" cy="12" r="10"/></svg>';
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 = `
<div id="check-personal" class="flex items-center space-x-3 p-2 rounded-lg transition">
<span class="check-icon text-gray-300">
<i data-lucide="circle" class="w-5 h-5"></i>
</span>
<span class="check-text text-sm font-medium">Información Personal</span>
</div>
<div id="check-summary" class="flex items-center space-x-3 p-2 rounded-lg transition">
<span class="check-icon text-gray-300">
<i data-lucide="circle" class="w-5 h-5"></i>
</span>
<span class="check-text text-sm font-medium">Resumen Profesional</span>
</div>
<div id="check-experience" class="flex items-center space-x-3 p-2 rounded-lg transition">
<span class="check-icon text-gray-300">
<i data-lucide="circle" class="w-5 h-5"></i>
</span>
<span class="check-text text-sm font-medium">Experiencia Laboral</span>
</div>
<div id="check-education" class="flex items-center space-x-3 p-2 rounded-lg transition">
<span class="check-icon text-gray-300">
<i data-lucide="circle" class="w-5 h-5"></i>
</span>
<span class="check-text text-sm font-medium">Educación</span>
</div>
<div id="check-skills" class="flex items-center space-x-3 p-2 rounded-lg transition">
<span class="check-icon text-gray-300">
<i data-lucide="circle" class="w-5 h-5"></i>
</span>
<span class="check-text text-sm font-medium">Habilidades</span>
</div>
<div id="check-ats" class="flex items-center space-x-3 p-2 rounded-lg transition">
<span class="check-icon text-gray-300">
<i data-lucide="circle" class="w-5 h-5"></i>
</span>
<span class="check-text text-sm font-medium">Optimización ATS</span>
</div>
`;
lucide.createIcons();
}
});