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

This commit is contained in:
Pablo de la Torre Jamardo 2025-07-22 08:06:10 +02:00
commit 9eca12ebca
23 changed files with 5069 additions and 0 deletions

34
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Deploy to GitHub Pages
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist

45
.gitignore vendored Normal file
View File

@ -0,0 +1,45 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build outputs
build/
out/
# Cache
.cache/
.parcel-cache/
# OS generated files
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/

243
README.md Normal file
View File

@ -0,0 +1,243 @@
# 🤖 AI Portfolio Chat
Un portfolio tecnológico innovador que simula una conversación con inteligencia artificial, pero funciona completamente offline con respuestas pre-programadas. Perfecto para impresionar a reclutadores y demostrar habilidades técnicas.
## 🚀 Características
- **Chat Interactivo**: Simula conversaciones naturales con IA
- **Respuestas Inteligentes**: Base de conocimiento contextual pre-programada
- **Diseño Moderno**: Interfaz atractiva con Tailwind CSS
- **Responsive**: Funciona perfectamente en móviles y desktop
- **Sin Dependencias Externas**: No requiere APIs ni servicios externos
- **Fácil Despliegue**: Listo para GitHub Pages
- **SEO Optimizado**: Metadatos y estructura optimizada
## 🛠️ Stack Tecnológico
- **Frontend**: Vue.js 3 (Composition API)
- **Styling**: Tailwind CSS
- **Icons**: Lucide Vue Next
- **Build Tool**: Vite
- **Deployment**: GitHub Pages
- **Node.js**: 18+
## 📦 Instalación
### Prerrequisitos
- Node.js 18 o superior
- npm o yarn
### Pasos de instalación
1. **Clonar el repositorio**
\`\`\`bash
git clone https://github.com/tu-usuario/ai-portfolio-chat.git
cd ai-portfolio-chat
\`\`\`
2. **Instalar dependencias**
\`\`\`bash
npm install
\`\`\`
3. **Ejecutar en desarrollo**
\`\`\`bash
npm run dev
\`\`\`
4. **Construir para producción**
\`\`\`bash
npm run build
\`\`\`
## 🚀 Despliegue
### GitHub Pages (Automático)
1. **Configurar el repositorio**:
- Ve a Settings > Pages
- Selecciona "GitHub Actions" como source
2. **Push a main**:
\`\`\`bash
git add .
git commit -m "Deploy portfolio"
git push origin main
\`\`\`
3. **El sitio estará disponible en**:
`https://tu-usuario.github.io/ai-portfolio-chat/`
### Despliegue Manual
\`\`\`bash
npm run build
npm run deploy
\`\`\`
## ⚙️ Personalización
### 1. Información Personal
Edita `src/composables/useKnowledgeBase.js`:
\`\`\`javascript
const knowledgeBase = ref({
experiencia: {
keywords: ['experiencia', 'trabajo', 'laboral'],
response: `Tu experiencia aquí...`
},
// ... más categorías
})
\`\`\`
### 2. Stack Tecnológico
Modifica el array en `src/App.vue`:
\`\`\`javascript
const techStack = [
'Tu', 'Stack', 'Tecnológico', 'Aquí'
]
\`\`\`
### 3. Información de Contacto
Actualiza los enlaces en `src/components/AppFooter.vue`:
\`\`\`vue
<a href="https://github.com/tu-usuario" target="_blank">
<a href="https://linkedin.com/in/tu-perfil" target="_blank">
<a href="mailto:tu.email@ejemplo.com">
\`\`\`
### 4. Metadatos SEO
Edita `index.html`:
\`\`\`html
<meta name="description" content="Tu descripción personalizada" />
<meta name="author" content="Tu Nombre" />
<meta property="og:title" content="Tu Título" />
\`\`\`
## 🎯 Características para Reclutadores
### Para RRHH:
- ✅ Interfaz intuitiva y profesional
- ✅ Información estructurada sobre experiencia
- ✅ Respuestas sobre expectativas salariales
- ✅ Datos de contacto fácilmente accesibles
### Para Líderes Técnicos:
- ✅ Demostración práctica de habilidades
- ✅ Código limpio y bien estructurado
- ✅ Arquitectura escalable con composables
- ✅ Implementación de mejores prácticas
## 📁 Estructura del Proyecto
\`\`\`
ai-portfolio-chat/
├── src/
│ ├── components/ # Componentes Vue
│ │ ├── AppHeader.vue
│ │ ├── WelcomeSection.vue
│ │ ├── ChatMessages.vue
│ │ ├── ChatInput.vue
│ │ ├── TechStack.vue
│ │ └── AppFooter.vue
│ ├── composables/ # Lógica reutilizable
│ │ ├── useKnowledgeBase.js
│ │ └── useChat.js
│ ├── App.vue # Componente principal
│ ├── main.js # Punto de entrada
│ └── style.css # Estilos globales
├── public/ # Archivos estáticos
├── .github/workflows/ # GitHub Actions
├── package.json
├── vite.config.js
├── tailwind.config.js
└── README.md
\`\`\`
## 🔧 Scripts Disponibles
\`\`\`bash
npm run dev # Servidor de desarrollo
npm run build # Build para producción
npm run preview # Preview del build
npm run deploy # Deploy a GitHub Pages
\`\`\`
## 🎨 Personalización de Estilos
El proyecto usa Tailwind CSS con configuración personalizada:
- **Colores**: Gradientes purple/pink para el tema principal
- **Animaciones**: Efectos suaves y profesionales
- **Responsive**: Mobile-first design
- **Dark Mode**: Soporte completo para tema oscuro
## 📱 Responsive Design
- **Mobile**: Optimizado para pantallas pequeñas
- **Tablet**: Layout adaptativo
- **Desktop**: Experiencia completa
## 🔍 SEO y Performance
- **Meta Tags**: Optimizados para redes sociales
- **Performance**: Lazy loading y code splitting
- **Accessibility**: ARIA labels y navegación por teclado
- **Core Web Vitals**: Optimizado para métricas de Google
## 🤝 Contribuciones
¡Las contribuciones son bienvenidas! Por favor:
1. Fork el proyecto
2. Crea una rama para tu feature
3. Commit tus cambios
4. Push a la rama
5. Abre un Pull Request
## 📄 Licencia
Este proyecto está bajo la Licencia MIT. Ver `LICENSE` para más detalles.
## 📞 Contacto
- **Email**: tu.email@ejemplo.com
- **LinkedIn**: [tu-perfil](https://linkedin.com/in/tu-perfil)
- **GitHub**: [tu-usuario](https://github.com/tu-usuario)
---
**¡Si te gusta este proyecto, dale una estrella en GitHub!**
\`\`\`
Archivo de licencia:
```text file="LICENSE"
MIT License
Copyright (c) 2024 Tu Nombre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

473
app.vue Normal file
View File

@ -0,0 +1,473 @@
<template>
<div class="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 text-white">
<!-- Header -->
<header class="border-b border-white/10 backdrop-blur-sm bg-black/20">
<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">
<span class="text-lg font-bold">AI</span>
</div>
<div>
<h1 class="text-xl font-bold">Portfolio Assistant</h1>
<p class="text-sm text-gray-300">Powered by Advanced AI</p>
</div>
</div>
<button
@click="toggleTheme"
class="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
>
<component :is="isDark ? 'Sun' : 'Moon'" class="w-5 h-5" />
</button>
</div>
</header>
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Welcome Section -->
<div v-if="messages.length === 0" class="text-center mb-8">
<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">
<Bot class="w-12 h-12" />
</div>
<h2 class="text-3xl font-bold mb-2">¡Hola! Soy tu Asistente de Portfolio</h2>
<p class="text-gray-300 text-lg">Pregúntame sobre experiencia, habilidades, proyectos o cualquier cosa técnica</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 in quickSuggestions"
:key="suggestion.text"
@click="sendMessage(suggestion.text)"
class="p-4 bg-white/5 hover:bg-white/10 rounded-xl border border-white/10 transition-all hover:scale-105 text-left"
>
<component :is="suggestion.icon" class="w-6 h-6 mb-2 text-purple-400" />
<h3 class="font-semibold mb-1">{{ suggestion.title }}</h3>
<p class="text-sm text-gray-400">{{ suggestion.text }}</p>
</button>
</div>
</div>
<!-- Chat Messages -->
<div class="space-y-4 mb-6" ref="messagesContainer">
<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" />
</div>
</div>
<div
class="max-w-xs lg:max-w-md px-4 py-2 rounded-2xl"
:class="message.role === 'user'
? 'bg-gradient-to-r from-blue-500 to-cyan-500 text-white'
: 'bg-white/10 backdrop-blur-sm border border-white/20'"
>
<div v-if="message.role === 'assistant' && message.typing" class="flex space-x-1">
<div class="w-2 h-2 bg-purple-400 rounded-full animate-bounce"></div>
<div class="w-2 h-2 bg-purple-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
<div class="w-2 h-2 bg-purple-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
</div>
<div v-else v-html="formatMessage(message.content)"></div>
</div>
</div>
</div>
<!-- Input Form -->
<form @submit.prevent="handleSubmit" class="relative">
<div class="flex space-x-2">
<input
v-model="input"
:disabled="isLoading"
placeholder="Pregúntame sobre mi experiencia, habilidades, proyectos..."
class="flex-1 px-4 py-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder-gray-400"
/>
<button
type="submit"
:disabled="isLoading || !input.trim()"
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"
>
<Send class="w-5 h-5" />
</button>
</div>
</form>
<!-- Tech Stack Display -->
<div class="mt-8 p-6 bg-white/5 backdrop-blur-sm rounded-xl border border-white/10">
<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 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">
{{ tech }}
</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick, onMounted } from 'vue'
import { Bot, User, Send, Sun, Moon, Code, Briefcase, Award, Rocket } from 'lucide-vue-next'
const messages = ref([])
const input = ref('')
const isLoading = ref(false)
const isDark = ref(true)
const messagesContainer = ref(null)
const techStack = [
'Vue.js', 'React', 'Node.js', 'TypeScript', 'Python', 'Docker',
'AWS', 'MongoDB', 'PostgreSQL', 'Git', 'CI/CD', 'Microservicios'
]
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'
}
]
// Base de conocimiento pre-programada
const knowledgeBase = {
experiencia: {
keywords: ['experiencia', 'trabajo', 'laboral', 'años', 'empresa', 'puesto'],
response: `
<strong>💼 Experiencia Profesional</strong><br><br>
<strong>Senior Full Stack Developer</strong> (2021 - Presente)<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><br>
<strong>Frontend Developer</strong> (2019 - 2021)<br>
Desarrollo de SPAs con Vue.js y React<br>
Optimización de performance (Core Web Vitals)<br>
Colaboración con equipos UX/UI<br><br>
<strong>Junior Developer</strong> (2018 - 2019)<br>
Desarrollo de APIs REST con Express.js<br>
Integración con bases de datos SQL y NoSQL<br>
Metodologías ágiles (Scrum/Kanban)
`
},
habilidades: {
keywords: ['habilidades', 'tecnologías', 'stack', 'lenguajes', 'frameworks', 'dominas'],
response: `
<strong>🚀 Stack Tecnológico</strong><br><br>
<strong>Frontend:</strong><br>
Vue.js 3 (Composition API, Pinia) - Avanzado<br>
React (Hooks, Context, Redux) - Avanzado<br>
TypeScript - Avanzado<br>
Tailwind CSS, SCSS - Avanzado<br><br>
<strong>Backend:</strong><br>
Node.js (Express, Fastify) - Avanzado<br>
Python (Django, FastAPI) - Intermedio<br>
Bases de datos: PostgreSQL, MongoDB - Avanzado<br><br>
<strong>DevOps & Cloud:</strong><br>
Docker, Kubernetes - Intermedio<br>
AWS (EC2, S3, Lambda) - Intermedio<br>
CI/CD (GitHub Actions, Jenkins) - Avanzado
`
},
proyectos: {
keywords: ['proyectos', 'desarrollado', 'creado', 'portfolio', 'destacados'],
response: `
<strong>🎯 Proyectos Destacados</strong><br><br>
<strong>E-commerce Platform</strong><br>
Plataforma completa con Vue.js + Node.js<br>
+50,000 usuarios activos mensuales<br>
Integración con Stripe y PayPal<br>
<em>Tech:</em> Vue 3, Express, PostgreSQL, Redis<br><br>
<strong>Real-time Analytics Dashboard</strong><br>
Dashboard en tiempo real con WebSockets<br>
Procesamiento de +1M eventos/día<br>
Visualizaciones interactivas con D3.js<br>
<em>Tech:</em> React, Socket.io, InfluxDB<br><br>
<strong>Microservices Architecture</strong><br>
Migración de monolito a microservicios<br>
Reducción de latencia en 60%<br>
Implementación con Docker y Kubernetes<br>
<em>Tech:</em> Node.js, Docker, AWS EKS
`
},
educacion: {
keywords: ['educación', 'estudios', 'universidad', 'carrera', 'certificaciones'],
response: `
<strong>🎓 Formación Académica</strong><br><br>
<strong>Ingeniería en Sistemas</strong><br>
Universidad Tecnológica Nacional (2014-2018)<br>
Especialización en Desarrollo de Software<br>
Proyecto final: Sistema de gestión hospitalaria<br><br>
<strong>Certificaciones:</strong><br>
AWS Certified Developer Associate (2022)<br>
MongoDB Certified Developer (2021)<br>
Scrum Master Certified (2020)<br><br>
<strong>Formación Continua:</strong><br>
Cursos especializados en arquitectura de software<br>
Participación en conferencias tech (JSConf, VueConf)<br>
Contribuciones a proyectos open source
`
},
contacto: {
keywords: ['contacto', 'email', 'linkedin', 'github', 'cv'],
response: `
<strong>📞 Información de Contacto</strong><br><br>
<strong>Email:</strong> tu.email@ejemplo.com<br>
<strong>LinkedIn:</strong> linkedin.com/in/tu-perfil<br>
<strong>GitHub:</strong> github.com/tu-usuario<br>
<strong>Portfolio:</strong> tu-portfolio.com<br><br>
<strong>Disponibilidad:</strong><br>
Disponible para nuevas oportunidades<br>
Modalidad: Remoto/Híbrido/Presencial<br>
Ubicación: Ciudad, País<br><br>
<em>¡No dudes en contactarme para discutir oportunidades!</em>
`
},
salario: {
keywords: ['salario', 'sueldo', 'pretensiones', 'económicas', 'remuneración'],
response: `
<strong>💰 Expectativas Salariales</strong><br><br>
Mis expectativas salariales son competitivas y están alineadas con:<br><br>
Mi experiencia de +5 años en desarrollo<br>
El mercado actual para Senior Developers<br>
La complejidad y responsabilidades del rol<br>
Los beneficios adicionales ofrecidos<br><br>
<em>Estoy abierto a discutir una propuesta integral que incluya salario base, beneficios y oportunidades de crecimiento.</em><br><br>
<strong>Factores importantes para :</strong><br>
Crecimiento profesional<br>
Ambiente de trabajo colaborativo<br>
Flexibilidad horaria<br>
Proyectos desafiantes
`
}
}
const defaultResponses = [
"Esa es una excelente pregunta. Como desarrollador senior, siempre busco mantenerme actualizado con las últimas tecnologías y mejores prácticas.",
"Interesante punto. En mi experiencia, he encontrado que la clave está en encontrar el equilibrio entre innovación y estabilidad.",
"Desde mi perspectiva técnica, considero que es fundamental evaluar cada herramienta en función del contexto específico del proyecto.",
"Basándome en mi experiencia en proyectos enterprise, puedo decir que la escalabilidad y mantenibilidad son aspectos cruciales.",
"Como alguien que ha trabajado tanto en startups como en empresas grandes, he aprendido a adaptar mi enfoque según las necesidades del negocio."
]
function findBestResponse(message) {
const lowerMessage = message.toLowerCase()
for (const [category, data] of Object.entries(knowledgeBase)) {
if (data.keywords.some(keyword => lowerMessage.includes(keyword))) {
return data.response
}
}
// Respuestas contextuales adicionales
if (lowerMessage.includes('react') || lowerMessage.includes('vue')) {
return `
<strong> React vs Vue.js</strong><br><br>
Tengo experiencia sólida con ambos frameworks:<br><br>
<strong>React:</strong><br>
Excelente ecosistema y comunidad<br>
Hooks y Context API para gestión de estado<br>
Ideal para aplicaciones complejas<br><br>
<strong>Vue.js:</strong><br>
Curva de aprendizaje más suave<br>
Composition API muy potente<br>
Excelente para desarrollo rápido<br><br>
<em>La elección depende del proyecto, equipo y requisitos específicos.</em>
`
}
if (lowerMessage.includes('node') || lowerMessage.includes('backend')) {
return `
<strong>🔧 Desarrollo Backend</strong><br><br>
Mi experiencia en backend incluye:<br><br>
<strong>Node.js:</strong> Express, Fastify, NestJS<br>
<strong>APIs:</strong> REST, GraphQL, WebSockets<br>
<strong>Bases de datos:</strong> PostgreSQL, MongoDB, Redis<br>
<strong>Arquitectura:</strong> Microservicios, Event-driven<br>
<strong>Testing:</strong> Jest, Mocha, Supertest<br><br>
<em>Siempre enfocado en código limpio, escalable y bien documentado.</em>
`
}
// Respuesta por defecto
return defaultResponses[Math.floor(Math.random() * defaultResponses.length)]
}
function formatMessage(content) {
return content.replace(/\n/g, '<br>')
}
async function sendMessage(text = null) {
const messageText = text || input.value.trim()
if (!messageText) return
// Agregar mensaje del usuario
const userMessage = {
id: Date.now(),
role: 'user',
content: messageText
}
messages.value.push(userMessage)
// Limpiar input
input.value = ''
isLoading.value = true
// Scroll to bottom
await nextTick()
scrollToBottom()
// Agregar mensaje de typing
const typingMessage = {
id: Date.now() + 1,
role: 'assistant',
content: '',
typing: true
}
messages.value.push(typingMessage)
await nextTick()
scrollToBottom()
// Simular delay de respuesta
setTimeout(() => {
// Remover mensaje de typing
messages.value.pop()
// Agregar respuesta real
const response = findBestResponse(messageText)
const assistantMessage = {
id: Date.now() + 2,
role: 'assistant',
content: response
}
messages.value.push(assistantMessage)
isLoading.value = false
nextTick(() => scrollToBottom())
}, 1500 + Math.random() * 1000) // Delay variable para mayor realismo
}
function handleSubmit() {
sendMessage()
}
function scrollToBottom() {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
}
}
function toggleTheme() {
isDark.value = !isDark.value
}
onMounted(() => {
// Mensaje de bienvenida después de un momento
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)
nextTick(() => scrollToBottom())
}, 1000)
})
</script>
<style scoped>
@keyframes bounce {
0%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
}
.animate-bounce {
animation: bounce 1s infinite;
}
/* 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);
}
</style>

31
index.html Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Portfolio tecnológico interactivo con asistente de IA - Desarrollador Full Stack especializado en Vue.js, React y Node.js" />
<meta name="keywords" content="desarrollador, full stack, vue.js, react, node.js, portfolio, programador" />
<meta name="author" content="Tu Nombre" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://tu-usuario.github.io/ai-portfolio-chat/" />
<meta property="og:title" content="Portfolio AI Chat - Desarrollador Full Stack" />
<meta property="og:description" content="Portfolio tecnológico interactivo con asistente de IA" />
<meta property="og:image" content="/og-image.jpg" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://tu-usuario.github.io/ai-portfolio-chat/" />
<meta property="twitter:title" content="Portfolio AI Chat - Desarrollador Full Stack" />
<meta property="twitter:description" content="Portfolio tecnológico interactivo con asistente de IA" />
<meta property="twitter:image" content="/og-image.jpg" />
<title>Portfolio AI Chat - Desarrollador Full Stack</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

3060
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "ai-portfolio-chat",
"version": "1.0.0",
"description": "Portfolio tecnológico interactivo con chat simulado de IA",
"type": "module",
"author": "Tu Nombre <tu.email@ejemplo.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tu-usuario/ai-portfolio-chat.git"
},
"homepage": "https://tu-usuario.github.io/ai-portfolio-chat/",
"keywords": [
"portfolio",
"vue",
"chat",
"ai",
"developer",
"frontend",
"javascript"
],
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"deploy": "npm run build && gh-pages -d dist",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"vue": "^3.4.21"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.21",
"gh-pages": "^6.1.1",
"lucide-vue-next": "^0.363.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"vite": "^5.2.8"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

156
src/App.vue Normal file
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>

BIN
src/assets/avatar-bot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

BIN
src/assets/avatar-user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

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>

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>

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>

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>

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>

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>

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,
}
}

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 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
src/main.js Normal file
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
src/style.css Normal file
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;
}

85
tailwind.config.js Normal file
View File

@ -0,0 +1,85 @@
const { fontFamily } = require("tailwindcss/defaultTheme");
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}", "*.{js,ts,jsx,tsx,mdx}"],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
50: "#f0f9ff",
100: "#e0f2fe",
200: "#bae6fd",
300: "#7dd3fc",
400: "#38bdf8",
500: "#0ea5e9",
600: "#0284c7",
700: "#0369a1",
800: "#075985",
900: "#0c4a6e",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
purple: {
400: "#a855f7",
500: "#9333ea",
600: "#7c3aed",
},
pink: {
500: "#ec4899",
600: "#db2777",
},
},
animation: {
"bounce-slow": "bounce 2s infinite",
"pulse-slow": "pulse 3s infinite",
"fade-in": "fadeIn 0.5s ease-in-out",
},
keyframes: {
fadeIn: {
"0%": { opacity: "0", transform: "translateY(10px)" },
"100%": { opacity: "1", transform: "translateY(0)" },
},
},
fontFamily: {
sans: ["Inter", ...fontFamily.sans],
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [],
};

30
vite.config.js Normal file
View File

@ -0,0 +1,30 @@
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import { resolve } from "path"
export default defineConfig({
plugins: [vue()],
base: process.env.NODE_ENV === "production" ? "/ai-portfolio-chat/" : "/",
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
build: {
outDir: "dist",
assetsDir: "assets",
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ["vue"],
icons: ["lucide-vue-next"],
},
},
},
},
server: {
port: 3000,
open: true,
},
})