Initial functional version of the portfolio chatbot site
Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
This commit is contained in:
commit
9eca12ebca
34
.github/workflows/deploy.yml
vendored
Normal file
34
.github/workflows/deploy.yml
vendored
Normal 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
45
.gitignore
vendored
Normal 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
243
README.md
Normal 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
473
app.vue
Normal 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 mí:</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
31
index.html
Normal 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
3060
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
package.json
Normal file
50
package.json
Normal 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
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
156
src/App.vue
Normal file
156
src/App.vue
Normal 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
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
BIN
src/assets/avatar-user.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 128 KiB |
44
src/components/AppFooter.vue
Normal file
44
src/components/AppFooter.vue
Normal 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>© {{ 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
src/components/AppHeader.vue
Normal file
57
src/components/AppHeader.vue
Normal 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
src/components/ChatInput.vue
Normal file
65
src/components/ChatInput.vue
Normal 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
src/components/ChatMessages.vue
Normal file
92
src/components/ChatMessages.vue
Normal 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
src/components/TechStack.vue
Normal file
26
src/components/TechStack.vue
Normal 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
src/components/WelcomeSection.vue
Normal file
64
src/components/WelcomeSection.vue
Normal 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
src/composables/useChat.js
Normal file
74
src/composables/useChat.js
Normal 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
src/composables/useKnowledgeBase.js
Normal file
337
src/composables/useKnowledgeBase.js
Normal 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
src/main.js
Normal file
12
src/main.js
Normal 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
85
src/style.css
Normal 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
85
tailwind.config.js
Normal 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
30
vite.config.js
Normal 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,
|
||||
},
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user