Aplicación web moderna para crear y compartir listas de deseos navideñas. Proyecto de aprendizaje y demostración de arquitectura full-stack con Vue 3 y Supabase.
GiftyList es una aplicación full-stack que permite a los usuarios crear listas de deseos navideñas, agregar regalos con extracción automática de imágenes desde URLs, y compartir estas listas mediante enlaces únicos. Los visitantes pueden reservar regalos de forma anónima o identificada, facilitando la coordinación de regalos sin arruinar sorpresas.
Este proyecto fue desarrollado como parte de mi portafolio profesional, demostrando competencias en:
- Arquitectura de aplicaciones modernas con Vue 3
- Integración con servicios Backend-as-a-Service (BaaS)
- Implementación de autenticación y autorización
- Diseño responsive y accesible
- DevOps y despliegue continuo
Desarrollado por: Esteban Cortes | Portfolio: eCortes.cl
- 📝 Gestión de Listas: Crear, editar y eliminar listas de deseos ilimitadas
- 🎁 Gestión de Regalos: Agregar regalos con URL, título, descripción y precio
- 🖼️ Extracción Automática: Obtención automática de imágenes desde URLs de productos
- 🔗 Compartir Listas: Generar enlaces únicos para compartir listas públicamente
- ✅ Sistema de Reservas: Reservar regalos con nombre o emoji identificador
- 🔐 Autenticación Completa: Registro, login, recuperación de contraseña
- 📱 Diseño Responsive: Optimizado para móvil, tablet y desktop
- 🎨 Tema Personalizado: Diseño navideño con paleta de colores festiva
- 🔒 Seguridad: Row Level Security (RLS) en base de datos
- 🚀 Optimización: Code splitting, lazy loading, y caching estratégico
- 🎯 SEO Optimizado: Meta tags dinámicos, Open Graph, Twitter Cards
- 🧪 Testing: Suite de tests con Vitest y Vue Test Utils
- 👨💼 Panel Admin: Dashboard administrativo para gestión de usuarios
-
Vue 3.5 (Composition API)
- Reactivity System con
refyreactive - Composables para lógica reutilizable
- Script Setup para componentes más limpios
- Reactivity System con
-
Vue Router 4.6
- Navegación declarativa con guards
- Lazy loading de rutas
- Protección de rutas autenticadas
-
Pinia 3.0 (State Management)
- Store modular para autenticación y estado global
- Persistencia de sesión
- Integración con Supabase Auth
-
Tailwind CSS 4.1
- Utility-first CSS framework
- Configuración personalizada con tema navideño
- Responsive design con mobile-first approach
-
Tabler Icons Vue 3.35
- Iconografía consistente y moderna
- Tree-shakeable para optimización de bundle
-
SweetAlert2 11.26
- Modales y alertas personalizadas
- UX mejorada para confirmaciones y notificaciones
- Vite 7.2
- Hot Module Replacement (HMR) ultra-rápido
- Build optimizado con Rollup
- Code splitting automático
- Minificación con Terser
-
Supabase Auth
- Autenticación con email/password
- Recuperación de contraseña
- Gestión de sesiones con JWT
-
Supabase Database (PostgreSQL)
- Base de datos relacional
- Row Level Security (RLS)
- Políticas de acceso granulares
- Índices optimizados para queries frecuentes
-
Supabase Storage (Opcional)
- Almacenamiento de imágenes
- CDN integrado
-- Tabla: wishlists
CREATE TABLE wishlists (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES auth.users NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
share_token VARCHAR(50) UNIQUE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Tabla: gift_items
CREATE TABLE gift_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
wishlist_id UUID REFERENCES wishlists ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
url TEXT,
image_url TEXT,
price DECIMAL(10,2),
reserved_by VARCHAR(100),
reserved_emoji VARCHAR(10),
reserved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);-- Los usuarios solo pueden ver/editar sus propias listas
CREATE POLICY "Users manage own wishlists"
ON wishlists FOR ALL
USING (auth.uid() = user_id);
-- Cualquiera puede ver listas compartidas públicamente
CREATE POLICY "Public can view shared wishlists"
ON wishlists FOR SELECT
USING (share_token IS NOT NULL);
-- Cualquiera puede reservar items de listas compartidas
CREATE POLICY "Public can reserve shared items"
ON gift_items FOR UPDATE
USING (EXISTS (
SELECT 1 FROM wishlists
WHERE id = gift_items.wishlist_id
AND share_token IS NOT NULL
));- Despliegue Automático: Push to deploy desde GitHub
- Preview Deployments: URLs únicas para cada PR
- Edge Network: CDN global para baja latencia
- Environment Variables: Gestión segura de secrets
- Analytics: Monitoreo de performance y Core Web Vitals
{
"framework": "vite",
"buildCommand": "pnpm run build",
"outputDirectory": "dist",
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
]
}- Code Splitting: Separación de vendor chunks (Vue, Supabase, UI)
- Tree Shaking: Eliminación de código no utilizado
- Minificación: Terser con eliminación de console.log
- Lazy Loading: Carga diferida de rutas y componentes
- Asset Optimization: Compresión de imágenes y assets
giftlist/
├── public/ # Assets estáticos
│ ├── .well-known/ # Configuración de plugins
│ ├── favicon.ico
│ ├── logo.png
│ ├── manifest.json # PWA manifest
│ ├── robots.txt # SEO crawlers
│ └── sitemap.xml # SEO sitemap
│
├── src/
│ ├── assets/ # Estilos globales
│ │ └── main.css # Tailwind + custom CSS
│ │
│ ├── components/
│ │ ├── atoms/ # Componentes básicos (Button, Input, Card)
│ │ ├── molecules/ # Componentes compuestos (Form, Modal)
│ │ └── admin/ # Componentes del panel admin
│ │
│ ├── composables/ # Lógica reutilizable
│ │ └── useSEO.js # Gestión de meta tags dinámicos
│ │
│ ├── lib/ # Configuraciones de librerías
│ │ └── supabase.js # Cliente de Supabase
│ │
│ ├── router/ # Configuración de rutas
│ │ └── index.js # Definición de rutas + guards
│ │
│ ├── stores/ # Pinia stores
│ │ └── auth.js # Store de autenticación
│ │
│ ├── types/ # Constantes y tipos
│ │ └── constants.js # Constantes de la app
│ │
│ ├── utils/ # Funciones utilitarias
│ │ ├── validators.js # Validaciones de formularios
│ │ └── formatters.js # Formateo de datos
│ │
│ ├── views/ # Páginas/Vistas
│ │ ├── HomePage.vue
│ │ ├── LoginPage.vue
│ │ ├── RegisterPage.vue
│ │ ├── DashboardPage.vue
│ │ ├── WishlistDetailPage.vue
│ │ ├── PublicWishlistPage.vue
│ │ └── AdminPage.vue
│ │
│ ├── App.vue # Componente raíz
│ └── main.js # Entry point
│
├── supabase/
│ ├── functions/ # Edge Functions (serverless)
│ └── migrations/ # Migraciones de base de datos
│ ├── 002_rls_policies.sql
│ ├── 003_admin_policies.sql
│ └── 004_verify_admin_setup.sql
│
├── scripts/
│ └── pre-deploy-check.sh # Validaciones pre-deploy
│
├── .env.example # Template de variables de entorno
├── vercel.json # Configuración de Vercel
├── vite.config.js # Configuración de Vite
├── vitest.config.js # Configuración de tests
└── package.json # Dependencias y scripts
Los componentes siguen el patrón Atomic Design:
- Atoms: Componentes básicos e indivisibles (Button, Input, Icon)
- Molecules: Combinación de atoms (SearchBar, Card, FormField)
- Organisms: Secciones complejas (Header, WishlistGrid, GiftForm)
Lógica reutilizable extraída en composables:
// composables/useSEO.js
export function useSEO() {
const setPageTitle = (title) => {
document.title = `${title} | GiftyList`
}
const setMetaTags = (tags) => {
// Gestión de meta tags dinámicos
}
return { setPageTitle, setMetaTags }
}- Node.js: v20.19.0 o superior (recomendado v22.12.0+)
- pnpm: v8.0.0 o superior (recomendado)
- Cuenta Supabase: Para backend y base de datos
- Cuenta Vercel: Para despliegue (opcional)
- Clonar el repositorio
git clone https://github.com/tu-usuario/giftlist.git
cd giftlist- Instalar dependencias
pnpm install
# o con npm
npm install- Configurar variables de entorno
cp .env.example .envEdita el archivo .env con tus credenciales:
# Supabase Configuration
VITE_SUPABASE_URL=https://tu-proyecto.supabase.co
VITE_SUPABASE_ANON_KEY=tu_clave_anonima_publica_aqui
# Application Configuration
VITE_APP_NAME=GiftyList
VITE_APP_URL=http://localhost:5173
# Optional: Analytics (si usas Vercel Analytics)
# VITE_VERCEL_ANALYTICS_ID=tu_analytics_id
⚠️ Nota de Seguridad: Nunca compartas tuSUPABASE_SERVICE_ROLE_KEY. Solo usaANON_KEYen el frontend.
- Configurar Supabase
Ejecuta las migraciones en tu proyecto de Supabase:
# Opción 1: Desde Supabase Dashboard
# - Ve a SQL Editor
# - Copia y ejecuta cada archivo de supabase/migrations/
# Opción 2: Con Supabase CLI
supabase db push- Iniciar servidor de desarrollo
pnpm devLa aplicación estará disponible en http://localhost:5173
# Desarrollo
pnpm dev # Inicia servidor de desarrollo con HMR
# Build
pnpm build # Build optimizado para producción
pnpm preview # Preview del build de producción
# Testing
pnpm test # Ejecuta tests en modo watch
pnpm test:ui # Abre interfaz visual de tests
pnpm test:run # Ejecuta tests una vez (CI mode)
# Deployment
pnpm deploy # Deploy a producción en Vercel
pnpm deploy:preview # Deploy preview en Vercel
pnpm pre-deploy # Validaciones pre-deployLas variables de entorno deben configurarse en:
- Local: Archivo
.env(no commitear) - Vercel: Dashboard > Settings > Environment Variables
Todas las tablas tienen RLS habilitado con políticas específicas:
- Autenticación requerida para operaciones CRUD en listas propias
- Acceso público solo para listas con
share_token - Reservas públicas permitidas solo en listas compartidas
- Admin access mediante políticas especiales
{
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block"
}- Vitest: Test runner ultra-rápido compatible con Vite
- Vue Test Utils: Utilidades oficiales para testing de Vue
- Happy DOM: Implementación ligera de DOM para tests
- Fast-check: Property-based testing
# Modo watch (desarrollo)
pnpm test
# Interfaz visual
pnpm test:ui
# Single run (CI)
pnpm test:runimport { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Button from '@/components/atoms/Button.vue'
describe('Button Component', () => {
it('renders correctly', () => {
const wrapper = mount(Button, {
props: { label: 'Click me' }
})
expect(wrapper.text()).toContain('Click me')
})
})- ✅ Meta tags dinámicos por página
- ✅ Open Graph tags para redes sociales
- ✅ Twitter Cards
- ✅ Sitemap.xml generado
- ✅ Robots.txt configurado
- ✅ Semantic HTML
- ✅ Structured data (JSON-LD)
- ✅ Lighthouse Score: 95+
- ✅ First Contentful Paint < 1.5s
- ✅ Time to Interactive < 3s
- ✅ Code splitting automático
- ✅ Lazy loading de imágenes
- ✅ Caching estratégico (1 año para assets)
Los colores se definen en src/assets/main.css:
:root {
--color-christmas-red: #ff5757;
--color-christmas-green: #165b33;
--color-christmas-gold: #ffd700;
--color-snow-white: #ffffff;
--color-dark-night: #1a1a1a;
}Reemplaza los archivos en public/:
logo.png- Logo principal de la aplicaciónlogo_ecortes.png- Logo del desarrolladorfavicon.ico- Favicon del sitio
-
Conectar repositorio
- Importa el proyecto desde GitHub en Vercel
- Vercel detectará automáticamente Vite
-
Configurar variables de entorno
- Agrega
VITE_SUPABASE_URL - Agrega
VITE_SUPABASE_ANON_KEY - Agrega
VITE_APP_URL(tu dominio de producción)
- Agrega
-
Deploy
pnpm deploy
# Build
pnpm build
# El directorio dist/ contiene los archivos estáticos
# Súbelos a cualquier hosting estático (Netlify, Cloudflare Pages, etc.)Este es un proyecto de aprendizaje y portafolio, pero las contribuciones son bienvenidas:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/NuevaCaracteristica) - Commit tus cambios (
git commit -m 'feat: Agregar nueva característica') - Push a la rama (
git push origin feature/NuevaCaracteristica) - Abre un Pull Request
Seguimos Conventional Commits:
feat:Nueva característicafix:Corrección de bugdocs:Cambios en documentaciónstyle:Cambios de formato (no afectan código)refactor:Refactorización de códigotest:Agregar o modificar testschore:Tareas de mantenimiento
- Mayor flexibilidad y reutilización de lógica
- Mejor TypeScript support (preparado para migración futura)
- Performance mejorado vs Options API
- Código más organizado y mantenible
- Backend completo sin servidor propio
- PostgreSQL con todas sus capacidades
- Row Level Security nativo
- Autenticación lista para usar
- Escalabilidad automática
- API más simple e intuitiva
- Mejor TypeScript support
- Menos boilerplate
- Recomendado oficialmente por Vue
- Desarrollo rápido con utility classes
- Bundle size optimizado (solo CSS usado)
- Consistencia en diseño
- Fácil personalización con tema
Este proyecto está bajo la Licencia MIT. Ver archivo LICENSE para más detalles.
MIT License
Copyright (c) 2024 Esteban Cortes
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...
Esteban Cortes
Full Stack Developer | Vue.js Specialist
- 🌐 Website: ecortes.cl
- 💼 LinkedIn: linkedin.com/in/ecortescl
- 📧 Email: contacto@ecortes.cl
- 🐙 GitHub: @ecortescl
- Vue.js Team - Por el increíble framework
- Vite Team - Por la herramienta de build más rápida
- Supabase Team - Por simplificar el backend
- Tailwind CSS Team - Por revolucionar el CSS
- Vercel Team - Por el mejor hosting para frontend
- Comunidad Open Source - Por todas las librerías utilizadas
- ✅ Versión: 1.0.0
- ✅ Estado: Producción
- ✅ Última actualización: Diciembre 2025
- ✅ Mantenimiento: Activo
Hecho con ❤️ y ☕ por eCortes.cl
⭐ Si este proyecto te fue útil, considera darle una estrella en GitHub