Initial functional version of the portfolio chatbot site
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled

This commit is contained in:
2025-07-22 08:06:10 +02:00
commit 9eca12ebca
23 changed files with 5069 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
<template>
<div :class="{ 'dark': isDark }" class="min-h-screen transition-colors duration-300">
<div class="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900 text-white">
<!-- Header Component -->
<AppHeader
:isDark="isDark"
@toggle-theme="toggleTheme"
:isOnline="isOnline"
/>
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Welcome Section -->
<WelcomeSection
v-if="messages.length === 0"
:quickSuggestions="quickSuggestions"
@send-message="sendMessage"
/>
<!-- Chat Messages -->
<ChatMessages
:messages="messages"
:isLoading="isLoading"
ref="chatMessages"
/>
<!-- Input Form -->
<ChatInput
:input="input"
:isLoading="isLoading"
@update:input="input = $event"
@send-message="handleSubmit"
/>
<!-- Tech Stack Display -->
<TechStack :techStack="techStack" />
<!-- Footer -->
<AppFooter />
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import AppHeader from './components/AppHeader.vue'
import WelcomeSection from './components/WelcomeSection.vue'
import ChatMessages from './components/ChatMessages.vue'
import ChatInput from './components/ChatInput.vue'
import TechStack from './components/TechStack.vue'
import AppFooter from './components/AppFooter.vue'
import { useKnowledgeBase } from './composables/useKnowledgeBase'
import { useChat } from './composables/useChat'
const isDark = ref(true)
const isOnline = ref(navigator.onLine)
// Composables
const { findBestResponse } = useKnowledgeBase()
const {
messages,
input,
isLoading,
sendMessage: sendChatMessage,
handleSubmit
} = useChat(findBestResponse)
const chatMessages = ref(null)
const techStack = [
'Vue.js 3', 'React 18', 'Node.js', 'TypeScript', 'Python', 'Docker',
'AWS', 'MongoDB', 'PostgreSQL', 'Git', 'CI/CD', 'Microservicios',
'Tailwind CSS', 'Express.js', 'FastAPI', 'Redis', 'Kubernetes'
]
const quickSuggestions = [
{
icon: 'Briefcase',
title: 'Experiencia',
text: '¿Cuál es tu experiencia laboral?'
},
{
icon: 'Code',
title: 'Habilidades',
text: '¿Qué tecnologías dominas?'
},
{
icon: 'Rocket',
title: 'Proyectos',
text: 'Cuéntame sobre tus proyectos destacados'
},
{
icon: 'GraduationCap',
title: 'Educación',
text: '¿Cuál es tu formación académica?'
},
{
icon: 'Mail',
title: 'Contacto',
text: '¿Cómo puedo contactarte?'
},
{
icon: 'DollarSign',
title: 'Salario',
text: '¿Cuáles son tus expectativas salariales?'
}
]
function sendMessage(text) {
sendChatMessage(text)
nextTick(() => {
if (chatMessages.value) {
chatMessages.value.scrollToBottom()
}
})
}
function toggleTheme() {
isDark.value = !isDark.value
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}
// Event listeners
onMounted(() => {
// Cargar tema guardado
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
isDark.value = savedTheme === 'dark'
}
// Listener para estado de conexión
window.addEventListener('online', () => isOnline.value = true)
window.addEventListener('offline', () => isOnline.value = false)
// Mensaje de bienvenida
setTimeout(() => {
const welcomeMessage = {
id: Date.now(),
role: 'assistant',
content: `
¡Hola! 👋 Soy tu asistente de portfolio inteligente.<br><br>
Puedo contarte sobre:<br>
• 💼 Mi experiencia profesional<br>
• 🚀 Habilidades técnicas<br>
• 🎯 Proyectos destacados<br>
• 🎓 Formación académica<br>
• 📞 Información de contacto<br><br>
<em>¿Qué te gustaría saber?</em>
`
}
//messages.value.push(welcomeMessage)
}, 1000)
})
</script>
Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+44
View File
@@ -0,0 +1,44 @@
<template>
<footer class="mt-12 pt-8 border-t border-white/10 text-center text-gray-400">
<div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
<div class="flex items-center space-x-4">
<a
href="https://github.com/tu-usuario"
target="_blank"
class="hover:text-white transition-colors"
>
<Github class="w-5 h-5" />
</a>
<a
href="https://linkedin.com/in/tu-perfil"
target="_blank"
class="hover:text-white transition-colors"
>
<Linkedin class="w-5 h-5" />
</a>
<a
href="mailto:tu.email@ejemplo.com"
class="hover:text-white transition-colors"
>
<Mail class="w-5 h-5" />
</a>
</div>
<div class="text-sm">
<p>&copy; {{ currentYear }} Tu Nombre. Hecho con y Vue.js</p>
</div>
<div class="text-xs">
<p>Versión 1.0.0 Node.js {{ nodeVersion }}</p>
</div>
</div>
</footer>
</template>
<script setup>
import { computed } from 'vue'
import { Github, Linkedin, Mail } from 'lucide-vue-next'
const currentYear = computed(() => new Date().getFullYear())
const nodeVersion = '18+'
</script>
+57
View File
@@ -0,0 +1,57 @@
<template>
<header class="border-b border-white/10 backdrop-blur-sm bg-black/20 sticky top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full flex items-center justify-center">
<img src="/src/assets/avatar-bot.png" alt="Avatar" class=" rounded-full object-cover" />
</div>
<div>
<h1 class="text-xl font-bold">Pablo de la Torre Jamardo</h1>
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-1">
<div
class="w-2 h-2 rounded-full"
:class="isOnline ? 'bg-green-400' : 'bg-red-400'"
></div>
<span class="text-xs text-gray-300">
{{ isOnline ? 'Online' : 'Offline' }}
</span>
</div>
<span class="text-xs text-gray-400"></span>
<span class="text-xs text-gray-300">Virtual Me, Powered by Code</span>
</div>
</div>
</div>
<div class="flex items-center space-x-2">
<button
@click="$emit('toggle-theme')"
class="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
:title="isDark ? 'Cambiar a tema claro' : 'Cambiar a tema oscuro'"
>
<component :is="isDark ? 'Sun' : 'Moon'" class="w-5 h-5" />
</button>
<a
href="https://github.com/tu-usuario/ai-portfolio-chat"
target="_blank"
class="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
title="Ver código en GitHub"
>
<Github class="w-5 h-5" />
</a>
</div>
</div>
</header>
</template>
<script setup>
import { Bot, Sun, Moon, Github } from 'lucide-vue-next'
defineProps({
isDark: Boolean,
isOnline: Boolean
})
defineEmits(['toggle-theme'])
</script>
+65
View File
@@ -0,0 +1,65 @@
<template>
<form @submit.prevent="handleSubmit" class="relative">
<div class="flex space-x-2">
<div class="flex-1 relative">
<input
:value="input"
@input="$emit('update:input', $event.target.value)"
:disabled="isLoading"
placeholder="Pregúntame sobre mi experiencia, habilidades, proyectos..."
class="w-full px-4 py-3 pr-12 glass-effect rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder-gray-400 transition-all"
@keydown.enter.prevent="handleSubmit"
/>
<!-- Character counter -->
<div class="absolute right-3 top-1/2 transform -translate-y-1/2 text-xs text-gray-500">
{{ input.length }}/500
</div>
</div>
<button
type="submit"
:disabled="isLoading || !input.trim() || input.length > 500"
class="px-6 py-3 bg-gradient-to-r from-purple-500 to-pink-500 rounded-xl font-semibold disabled:opacity-50 disabled:cursor-not-allowed hover:from-purple-600 hover:to-pink-600 transition-all transform hover:scale-105 active:scale-95"
>
<Send v-if="!isLoading" class="w-5 h-5" />
<div v-else class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
</button>
</div>
<!-- Quick suggestions -->
<div v-if="!input && quickSuggestions.length > 0" class="flex flex-wrap gap-2 mt-3">
<button
v-for="suggestion in quickSuggestions.slice(0, 3)"
:key="suggestion"
@click="$emit('update:input', suggestion)"
class="px-3 py-1 text-sm bg-white/5 hover:bg-white/10 rounded-full border border-white/10 transition-colors"
>
{{ suggestion }}
</button>
</div>
</form>
</template>
<script setup>
import { Send } from 'lucide-vue-next'
const props = defineProps({
input: String,
isLoading: Boolean
})
const emit = defineEmits(['update:input', 'send-message'])
const quickSuggestions = [
'¿Cuál es tu experiencia?',
'¿Qué tecnologías usas?',
'Háblame de tus proyectos'
]
function handleSubmit() {
if (props.input.trim() && !props.isLoading && props.input.length <= 500) {
emit('send-message')
}
}
</script>
+92
View File
@@ -0,0 +1,92 @@
<template>
<div class="space-y-4 mb-6 max-h-96 overflow-y-auto" ref="messagesContainer">
<TransitionGroup name="chat-message" tag="div">
<div
v-for="message in messages"
:key="message.id"
class="flex items-start space-x-3"
:class="message.role === 'user' ? 'flex-row-reverse space-x-reverse' : ''"
>
<div class="flex-shrink-0">
<div
class="w-8 h-8 rounded-full flex items-center justify-center"
:class="message.role === 'user'
? 'bg-gradient-to-r from-blue-500 to-cyan-500'
: 'bg-gradient-to-r from-purple-500 to-pink-500'"
>
<component :is="message.role === 'user' ? 'User' : 'Bot'" class="w-4 h-4" />
<img
:src="message.role === 'user' ? avatarUser : avatarBot"
alt="avatar"
class="w-8 h-8 rounded-full object-cover"
/>
</div>
</div>
<div
class="max-w-xs lg:max-w-md px-4 py-3 rounded-2xl"
:class="message.role === 'user'
? 'bg-gradient-to-r from-blue-500 to-cyan-500 text-white'
: 'glass-effect'"
>
<!-- Typing indicator -->
<div v-if="message.role === 'assistant' && message.typing" class="flex space-x-1">
<div class="w-2 h-2 bg-purple-400 rounded-full typing-indicator"></div>
<div class="w-2 h-2 bg-purple-400 rounded-full typing-indicator"></div>
<div class="w-2 h-2 bg-purple-400 rounded-full typing-indicator"></div>
</div>
<!-- Message content -->
<div v-else>
<div v-html="formatMessage(message.content)" class="prose prose-invert max-w-none"></div>
<div v-if="message.role === 'assistant'" class="text-xs text-gray-400 mt-2">
{{ formatTime(message.timestamp) }}
</div>
</div>
</div>
</div>
</TransitionGroup>
</div>
</template>
<script setup>
import { ref, nextTick, watch } from 'vue'
import avatarUser from './../assets/avatar-user.jpg'
import avatarBot from './../assets/avatar-bot.png'
const props = defineProps({
messages: Array,
isLoading: Boolean
})
const messagesContainer = ref(null)
function formatMessage(content) {
return content.replace(/\n/g, '<br>')
}
function formatTime(timestamp) {
if (!timestamp) return ''
return new Date(timestamp).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
})
}
function scrollToBottom() {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
}
}
// Watch for new messages and scroll to bottom
watch(() => props.messages.length, () => {
nextTick(() => scrollToBottom())
})
// Expose scrollToBottom method
defineExpose({
scrollToBottom
})
</script>
+26
View File
@@ -0,0 +1,26 @@
<template>
<div class="mt-8 p-6 glass-effect rounded-xl">
<h3 class="text-lg font-semibold mb-4 flex items-center">
<Code class="w-5 h-5 mr-2 text-purple-400" />
Stack Tecnológico Principal
</h3>
<div class="flex flex-wrap gap-2">
<span
v-for="(tech, index) in techStack"
:key="tech"
class="px-3 py-1 bg-purple-500/20 text-purple-300 rounded-full text-sm border border-purple-500/30 hover:bg-purple-500/30 transition-colors cursor-default"
:style="{ animationDelay: `${index * 50}ms` }"
>
{{ tech }}
</span>
</div>
</div>
</template>
<script setup>
import { Code } from 'lucide-vue-next'
defineProps({
techStack: Array
})
</script>
+64
View File
@@ -0,0 +1,64 @@
<template>
<div class="text-center mb-8 animate-fade-in">
<div class="mb-6">
<div class="w-24 h-24 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full mx-auto mb-4 flex items-center justify-center animate-pulse-slow">
<img src="/src/assets/avatar-bot.png" alt="Avatar" class="rounded-full object-cover" />
</div>
<h2 class="text-3xl font-bold mb-2 gradient-text">
¡Hola! Soy tu asistente personal de portfolio
</h2>
<p class="text-gray-300 text-lg max-w-2xl mx-auto">
Pregúntame sobre mi experiencia, habilidades, proyectos o cualquier detalle técnico.
¡Estoy listo para charlar y ayudarte a descubrir mi perfil profesional!
</p>
</div>
<!-- Quick Actions -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
<button
v-for="(suggestion, index) in quickSuggestions"
:key="suggestion.text"
@click="$emit('send-message', suggestion.text)"
class="p-4 glass-effect rounded-xl transition-all hover:scale-105 hover:bg-white/15 text-left group"
:style="{ animationDelay: `${index * 100}ms` }"
>
<component
:is="suggestion.icon"
class="w-6 h-6 mb-2 text-purple-400 group-hover:text-purple-300 transition-colors"
/>
<h3 class="font-semibold mb-1 text-white group-hover:text-purple-100 transition-colors">
{{ suggestion.title }}
</h3>
<p class="text-sm text-gray-400 group-hover:text-gray-300 transition-colors">
{{ suggestion.text }}
</p>
</button>
</div>
<!-- Stats -->
<div class="flex justify-center space-x-8 text-sm text-gray-400">
<div class="text-center">
<div class="text-2xl font-bold text-purple-400">5+</div>
<div>Años Experiencia</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-purple-400">50+</div>
<div>Proyectos</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-purple-400">15+</div>
<div>Tecnologías</div>
</div>
</div>
</div>
</template>
<script setup>
import { Bot, Briefcase, Code, Rocket, GraduationCap, Mail, DollarSign } from 'lucide-vue-next'
defineProps({
quickSuggestions: Array
})
defineEmits(['send-message'])
</script>
+74
View File
@@ -0,0 +1,74 @@
import { ref } from "vue"
export function useChat(findBestResponse) {
const messages = ref([])
const input = ref("")
const isLoading = ref(false)
async function sendMessage(text = null) {
const messageText = text || input.value.trim()
if (!messageText || isLoading.value) return
// Agregar mensaje del usuario
const userMessage = {
id: Date.now(),
role: "user",
content: messageText,
timestamp: Date.now(),
}
messages.value.push(userMessage)
// Limpiar input
input.value = ""
isLoading.value = true
// Agregar mensaje de typing
const typingMessage = {
id: Date.now() + 1,
role: "assistant",
content: "",
typing: true,
}
messages.value.push(typingMessage)
// Simular delay de respuesta realista
const delay = 1000 + Math.random() * 2000 // 1-3 segundos
setTimeout(() => {
// Remover mensaje de typing
messages.value.pop()
// Obtener respuesta de la base de conocimiento
const responseData = findBestResponse(messageText)
// Agregar respuesta real
const assistantMessage = {
id: Date.now() + 2,
role: "assistant",
content: responseData.content,
category: responseData.category,
timestamp: responseData.timestamp,
}
messages.value.push(assistantMessage)
isLoading.value = false
}, delay)
}
function handleSubmit() {
sendMessage()
}
function clearChat() {
messages.value = []
}
return {
messages,
input,
isLoading,
sendMessage,
handleSubmit,
clearChat,
}
}
+337
View File
@@ -0,0 +1,337 @@
import { ref } from "vue"
export function useKnowledgeBase() {
const knowledgeBase = ref({
experiencia: {
keywords: ["experiencia", "trabajo", "laboral", "años", "empresa", "puesto", "carrera"],
response: `
<strong>💼 Experiencia Profesional</strong><br><br>
<strong>Senior Full Stack Developer</strong> (2021 - Presente)<br>
<em>TechCorp Solutions</em><br>
• Liderazgo de equipo de 5 desarrolladores<br>
• Arquitectura de microservicios con Node.js y Docker<br>
• Implementación de CI/CD reduciendo deploys en 80%<br>
• Migración de aplicaciones legacy a arquitecturas modernas<br><br>
<strong>Frontend Developer</strong> (2019 - 2021)<br>
<em>StartupXYZ</em><br>
• Desarrollo de SPAs con Vue.js y React<br>
• Optimización de performance (Core Web Vitals)<br>
• Colaboración estrecha con equipos UX/UI<br>
• Implementación de testing automatizado<br><br>
<strong>Junior Developer</strong> (2018 - 2019)<br>
<em>DevAgency</em><br>
• Desarrollo de APIs REST con Express.js<br>
• Integración con bases de datos SQL y NoSQL<br>
• Metodologías ágiles (Scrum/Kanban)<br>
• Participación en code reviews y pair programming
`,
},
habilidades: {
keywords: ["habilidades", "tecnologías", "stack", "lenguajes", "frameworks", "dominas", "herramientas"],
response: `
<strong>🚀 Stack Tecnológico</strong><br><br>
<strong>Frontend (Avanzado):</strong><br>
• Vue.js 3 (Composition API, Pinia, Nuxt.js)<br>
• React 18 (Hooks, Context, Redux Toolkit, Next.js)<br>
• TypeScript - Tipado fuerte y desarrollo escalable<br>
• Tailwind CSS, SCSS - Diseño responsive y modular<br>
• Webpack, Vite - Bundling y optimización<br><br>
<strong>Backend (Avanzado):</strong><br>
• Node.js (Express, Fastify, NestJS)<br>
• Python (Django, FastAPI) - APIs y microservicios<br>
• Bases de datos: PostgreSQL, MongoDB, Redis<br>
• GraphQL, REST APIs - Diseño de APIs escalables<br><br>
<strong>DevOps & Cloud (Intermedio-Avanzado):</strong><br>
• Docker, Kubernetes - Containerización<br>
• AWS (EC2, S3, Lambda, RDS) - Cloud computing<br>
• CI/CD (GitHub Actions, Jenkins) - Automatización<br>
• Nginx, Apache - Configuración de servidores<br><br>
<strong>Herramientas & Metodologías:</strong><br>
• Git (GitFlow, conventional commits)<br>
• Jest, Cypress - Testing automatizado<br>
• Scrum, Kanban - Metodologías ágiles<br>
• Figma, Adobe XD - Colaboración con diseño
`,
},
proyectos: {
keywords: ["proyectos", "desarrollado", "creado", "portfolio", "destacados", "aplicaciones"],
response: `
<strong>🎯 Proyectos Destacados</strong><br><br>
<strong>🛒 E-commerce Platform</strong><br>
<em>Plataforma completa de comercio electrónico</em><br>
• +50,000 usuarios activos mensuales<br>
• Integración con múltiples pasarelas de pago<br>
• Panel de administración con analytics en tiempo real<br>
• <strong>Tech:</strong> Vue 3, Node.js, PostgreSQL, Redis, Stripe<br>
• <strong>Logros:</strong> 99.9% uptime, 2s tiempo de carga<br><br>
<strong>📊 Real-time Analytics Dashboard</strong><br>
<em>Dashboard empresarial con visualizaciones interactivas</em><br>
• Procesamiento de +1M eventos/día<br>
• Visualizaciones en tiempo real con WebSockets<br>
• Exportación de reportes automatizada<br>
• <strong>Tech:</strong> React, D3.js, Socket.io, InfluxDB<br>
• <strong>Logros:</strong> Reducción de 70% en tiempo de análisis<br><br>
<strong>🏗️ Microservices Architecture</strong><br>
<em>Migración de monolito a microservicios</em><br>
• Arquitectura distribuida con 12 microservicios<br>
• Implementación de Event Sourcing y CQRS<br>
• Monitoreo con Prometheus y Grafana<br>
• <strong>Tech:</strong> Node.js, Docker, Kubernetes, AWS EKS<br>
• <strong>Logros:</strong> 60% reducción latencia, 99.95% disponibilidad<br><br>
<strong>🤖 AI Portfolio Chat</strong> (Este proyecto)<br>
<em>Portfolio interactivo con simulación de IA</em><br>
• Chat inteligente sin dependencias externas<br>
• Respuestas contextuales pre-programadas<br>
• Diseño responsive y accesible<br>
• <strong>Tech:</strong> Vue 3, Tailwind CSS, Vite<br>
• <strong>Innovación:</strong> Portfolio que demuestra habilidades técnicas
`,
},
educacion: {
keywords: ["educación", "estudios", "universidad", "carrera", "certificaciones", "formación"],
response: `
<strong>🎓 Formación Académica</strong><br><br>
<strong>Ingeniería en Sistemas de Información</strong><br>
<em>Universidad Tecnológica Nacional (2014-2018)</em><br>
• Especialización en Desarrollo de Software<br>
• Proyecto final: Sistema de gestión hospitalaria<br>
• Promedio: 8.5/10<br><br>
<strong>Certificaciones Profesionales:</strong><br>
• <strong>AWS Certified Developer Associate</strong> (2022)<br>
• <strong>MongoDB Certified Developer</strong> (2021)<br>
• <strong>Certified Scrum Master (CSM)</strong> (2020)<br>
• <strong>Google Cloud Professional Developer</strong> (2023)<br><br>
<strong>Formación Continua:</strong><br>
• <strong>Arquitectura de Software</strong> - Platzi (2023)<br>
• <strong>Advanced React Patterns</strong> - Epic React (2022)<br>
• <strong>Microservices with Node.js</strong> - Udemy (2021)<br>
• <strong>Machine Learning Fundamentals</strong> - Coursera (2023)<br><br>
<strong>Participación en Comunidad:</strong><br>
• Speaker en VueConf Argentina 2022<br>
• Contribuciones a proyectos open source<br>
• Mentor en programas de coding bootcamps<br>
• Organizador de meetups locales de JavaScript
`,
},
contacto: {
keywords: ["contacto", "email", "linkedin", "github", "cv", "ubicación", "teléfono"],
response: `
<strong>📞 Información de Contacto</strong><br><br>
<strong>Datos Principales:</strong><br>
• <strong>Email:</strong> tu.email@ejemplo.com<br>
• <strong>LinkedIn:</strong> <a href="https://linkedin.com/in/tu-perfil" target="_blank" class="text-blue-400 hover:underline">linkedin.com/in/tu-perfil</a><br>
• <strong>GitHub:</strong> <a href="https://github.com/tu-usuario" target="_blank" class="text-blue-400 hover:underline">github.com/tu-usuario</a><br>
• <strong>Portfolio:</strong> <a href="https://tu-portfolio.com" target="_blank" class="text-blue-400 hover:underline">tu-portfolio.com</a><br><br>
<strong>Ubicación & Disponibilidad:</strong><br>
• <strong>Ubicación:</strong> Buenos Aires, Argentina<br>
• <strong>Zona horaria:</strong> GMT-3 (Argentina)<br>
• <strong>Modalidad:</strong> Remoto/Híbrido/Presencial<br>
• <strong>Disponibilidad:</strong> Inmediata<br><br>
<strong>Idiomas:</strong><br>
• <strong>Español:</strong> Nativo<br>
• <strong>Inglés:</strong> Avanzado (C1) - Certificado Cambridge<br>
• <strong>Portugués:</strong> Intermedio (B2)<br><br>
<strong>Horarios de Contacto:</strong><br>
• Lunes a Viernes: 9:00 - 18:00 (GMT-3)<br>
• Respuesta garantizada en menos de 24hs<br><br>
<em>¡No dudes en contactarme para discutir oportunidades laborales!</em>
`,
},
salario: {
keywords: ["salario", "sueldo", "pretensiones", "económicas", "remuneración", "dinero", "pago"],
response: `
<strong>💰 Expectativas Salariales</strong><br><br>
<strong>Rango Salarial (USD/mes):</strong><br>
• <strong>Remoto Internacional:</strong> $4,000 - $6,000<br>
• <strong>Empresas Locales:</strong> $2,500 - $4,000<br>
• <strong>Freelance/Consultoría:</strong> $50 - $80/hora<br><br>
<strong>Factores que Considero:</strong><br>
• Complejidad técnica del proyecto<br>
• Responsabilidades de liderazgo<br>
• Oportunidades de crecimiento profesional<br>
• Beneficios adicionales (salud, vacaciones, etc.)<br>
• Cultura y ambiente de trabajo<br>
• Modalidad de trabajo (remoto/híbrido/presencial)<br><br>
<strong>Beneficios Valorados:</strong><br>
• 🏥 Cobertura médica completa<br>
• 📚 Presupuesto para capacitación y conferencias<br>
• 💻 Equipamiento de trabajo de calidad<br>
• 🏖️ Días de vacaciones flexibles<br>
• 🚀 Stock options o participación en ganancias<br>
• 🏠 Flexibilidad horaria y trabajo remoto<br><br>
<strong>Modalidades de Contratación:</strong><br>
• Relación de dependencia (preferida)<br>
• Contrato por proyecto<br>
• Consultoría a largo plazo<br><br>
<em>Estoy abierto a negociar un paquete integral que sea beneficioso para ambas partes. Lo más importante para mí es encontrar un proyecto desafiante con un equipo talentoso.</em>
`,
},
})
const defaultResponses = [
"Esa es una excelente pregunta. Como desarrollador senior con más de 5 años de experiencia, siempre busco mantenerme actualizado con las últimas tecnologías y mejores prácticas del desarrollo web.",
"Interesante punto. En mi experiencia trabajando tanto en startups como en empresas enterprise, he encontrado que la clave está en encontrar el equilibrio entre innovación y estabilidad.",
"Desde mi perspectiva técnica, considero fundamental evaluar cada herramienta y tecnología en función del contexto específico del proyecto y las necesidades del negocio.",
"Basándome en mi experiencia liderando equipos y desarrollando arquitecturas escalables, puedo decir que la comunicación y la documentación son tan importantes como el código.",
"Como alguien que ha migrado sistemas legacy y implementado arquitecturas modernas, he aprendido que la planificación y el testing son cruciales para el éxito de cualquier proyecto.",
]
function findBestResponse(message) {
const lowerMessage = message.toLowerCase()
// Buscar en la base de conocimiento
for (const [category, data] of Object.entries(knowledgeBase.value)) {
if (data.keywords.some((keyword) => lowerMessage.includes(keyword))) {
return {
content: data.response,
category: category,
timestamp: Date.now(),
}
}
}
// Respuestas contextuales específicas
if (lowerMessage.includes("react") || lowerMessage.includes("vue")) {
return {
content: `
<strong>⚛️ React vs Vue.js - Mi Perspectiva</strong><br><br>
Tengo experiencia sólida con ambos frameworks y los uso según el contexto:<br><br>
<strong>React:</strong><br>
• Ecosistema maduro y comunidad muy activa<br>
• Hooks y Context API para gestión de estado elegante<br>
• Ideal para aplicaciones complejas y equipos grandes<br>
• Excelente para desarrollo de componentes reutilizables<br><br>
<strong>Vue.js:</strong><br>
• Curva de aprendizaje más suave y sintaxis intuitiva<br>
• Composition API muy potente (similar a React Hooks)<br>
• Excelente para desarrollo rápido y prototipado<br>
• Mejor integración con proyectos existentes<br><br>
<strong>Mi Recomendación:</strong><br>
• <em>React:</em> Para SPAs complejas, equipos grandes, ecosistema robusto<br>
• <em>Vue:</em> Para desarrollo ágil, equipos pequeños, integración gradual<br><br>
<em>En mi experiencia, ambos son excelentes herramientas. La elección depende del proyecto, equipo y contexto específico.</em>
`,
category: "tecnologias",
timestamp: Date.now(),
}
}
if (lowerMessage.includes("node") || lowerMessage.includes("backend") || lowerMessage.includes("servidor")) {
return {
content: `
<strong>🔧 Desarrollo Backend con Node.js</strong><br><br>
Mi experiencia en backend se centra principalmente en el ecosistema JavaScript:<br><br>
<strong>Frameworks y Librerías:</strong><br>
• <strong>Express.js:</strong> Framework minimalista, ideal para APIs REST<br>
• <strong>Fastify:</strong> Alto rendimiento, excelente para microservicios<br>
• <strong>NestJS:</strong> Arquitectura escalable, perfecto para aplicaciones enterprise<br><br>
<strong>Bases de Datos:</strong><br>
• <strong>PostgreSQL:</strong> Para datos relacionales complejos<br>
• <strong>MongoDB:</strong> Para datos no estructurados y prototipado rápido<br>
• <strong>Redis:</strong> Para caché y sesiones de usuario<br><br>
<strong>Arquitecturas que Manejo:</strong><br>
• APIs REST con documentación OpenAPI/Swagger<br>
• GraphQL para consultas flexibles<br>
• Microservicios con comunicación asíncrona<br>
• Event-driven architecture con message queues<br><br>
<strong>Mejores Prácticas:</strong><br>
• Testing automatizado (Jest, Supertest)<br>
• Validación de datos con Joi/Yup<br>
• Logging estructurado con Winston<br>
• Monitoreo con Prometheus y Grafana<br><br>
<em>Siempre enfocado en código limpio, escalable y bien documentado.</em>
`,
category: "backend",
timestamp: Date.now(),
}
}
if (lowerMessage.includes("docker") || lowerMessage.includes("kubernetes") || lowerMessage.includes("devops")) {
return {
content: `
<strong>🐳 DevOps y Containerización</strong><br><br>
Mi experiencia en DevOps se enfoca en automatización y escalabilidad:<br><br>
<strong>Containerización:</strong><br>
• <strong>Docker:</strong> Creación de imágenes optimizadas multi-stage<br>
• <strong>Docker Compose:</strong> Orquestación local y testing<br>
• <strong>Kubernetes:</strong> Despliegue y escalado en producción<br><br>
<strong>CI/CD Pipelines:</strong><br>
• <strong>GitHub Actions:</strong> Automatización completa de workflows<br>
• <strong>Jenkins:</strong> Pipelines complejos para empresas<br>
• Testing automatizado, build y deploy<br><br>
<strong>Cloud Platforms:</strong><br>
• <strong>AWS:</strong> EC2, ECS, Lambda, RDS, S3<br>
• <strong>Google Cloud:</strong> GKE, Cloud Functions, Cloud SQL<br>
• Infrastructure as Code con Terraform<br><br>
<strong>Monitoreo y Observabilidad:</strong><br>
• Prometheus + Grafana para métricas<br>
• ELK Stack para logs centralizados<br>
• Health checks y alertas automatizadas<br><br>
<em>Mi objetivo es crear pipelines que permitan deploys seguros y frecuentes, reduciendo el time-to-market.</em>
`,
category: "devops",
timestamp: Date.now(),
}
}
// Respuesta por defecto
const randomResponse = defaultResponses[Math.floor(Math.random() * defaultResponses.length)]
return {
content: randomResponse,
category: "general",
timestamp: Date.now(),
}
}
return {
findBestResponse,
knowledgeBase,
}
}
+12
View File
@@ -0,0 +1,12 @@
import { createApp } from "vue"
import App from "./App.vue"
import "./style.css"
const app = createApp(App)
// Global error handler
app.config.errorHandler = (err, vm, info) => {
console.error("Global error:", err, info)
}
app.mount("#app")
+85
View File
@@ -0,0 +1,85 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
scroll-behavior: smooth;
}
body {
@apply antialiased;
}
* {
@apply border-border;
}
}
@layer components {
.gradient-text {
@apply bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent;
}
.glass-effect {
@apply backdrop-blur-sm bg-white/10 border border-white/20;
}
.chat-message-enter-active,
.chat-message-leave-active {
transition: all 0.3s ease;
}
.chat-message-enter-from {
opacity: 0;
transform: translateY(20px);
}
.chat-message-leave-to {
opacity: 0;
transform: translateX(-20px);
}
}
/* Scrollbar personalizado */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: rgba(147, 51, 234, 0.5);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(147, 51, 234, 0.7);
}
/* Animaciones personalizadas */
@keyframes typing {
0%,
60%,
100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
.typing-indicator {
animation: typing 1.4s infinite;
}
.typing-indicator:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator:nth-child(3) {
animation-delay: 0.4s;
}