MemoryNinja es una aplicación web moderna de generación automática de tarjetas de estudio (flashcards) potenciada por inteligencia artificial. Permite a los usuarios crear, organizar y estudiar flashcards personalizadas según sus temas de interés, con sincronización en tiempo real y una experiencia de usuario excepcional.
- ✨ Características Principales
- 🏗️ Arquitectura del Sistema
- 📂 Estructura del Proyecto
- 🛠️ Stack Tecnológico
- 🚀 Instalación y Configuración
- 📡 API y Endpoints
- 🧩 Componentes Principales
- 🔄 Gestión de Estado
- 🎨 Sistema de Animaciones
- 🔐 Autenticación y Seguridad
- 🧪 Testing
- 📊 Características Técnicas Avanzadas
- 🌐 Despliegue
- 🤝 Contribuir
- 📄 Licencia
- ✅ Generación Automática con IA: Crea flashcards personalizadas usando dos modelos de IA:
- Kōga (甲賀): Modelo básico, rápido y preciso para respuestas confiables
- Kurayami (暗闇): Modelo premium con respuestas profundas y avanzadas
- 📚 Gestión de Temas Personalizados: Crea y administra temas de estudio según tus intereses
- 🔄 Sincronización en Tiempo Real: Los datos se sincronizan automáticamente entre dispositivos
- 📊 Dashboard Analítico: Visualiza estadísticas de tu progreso de estudio
- 🎯 Sistema de Onboarding: Proceso guiado de configuración inicial para nuevos usuarios
- 🌙 Tema Oscuro: Interfaz optimizada para reducir fatiga visual
- 📱 Diseño Responsivo: Experiencia optimizada en móviles, tablets y escritorio
- ⚡ Rendimiento Optimizado: Turbopack, caché inteligente y lazy loading
- 🔒 Rate Limiting: Protección contra abuso con límites configurables por endpoint
- 🎭 Animaciones Accesibles: Sistema de animaciones con soporte para
prefers-reduced-motion - 🧪 Testing E2E: Suite completa de pruebas con Playwright
- 📈 Observabilidad: Monitoreo y logging estructurado de errores
- 🔄 Gestión de Errores Robusta: Manejo de estados de carga, error y reconexión
MemoryNinja sigue una arquitectura hexagonal (ports & adapters) con separación clara de responsabilidades:
┌─────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ (Next.js App Router, React Components, UI) │
└───────────────────┬─────────────────────────────────────┘
│
┌───────────────────▼─────────────────────────────────────┐
│ APPLICATION LAYER │
│ (Hooks, Stores, Business Logic, React Query) │
└───────────────────┬─────────────────────────────────────┘
│
┌───────────────────▼─────────────────────────────────────┐
│ DOMAIN LAYER │
│ (Entities, Types, Interfaces, Business Rules) │
└───────────────────┬─────────────────────────────────────┘
│
┌───────────────────▼─────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ (Repositories, API Clients, External Services) │
└─────────────────────────────────────────────────────────┘
- Separación de Responsabilidades: Cada capa tiene responsabilidades bien definidas
- Inversión de Dependencias: Las capas superiores no dependen de las inferiores
- Unit of Work Pattern: Gestión transaccional de operaciones con flashcards
- Repository Pattern: Abstracción del acceso a datos
- Custom Hooks: Lógica de negocio reutilizable y componentes limpios
- Server Components: Renderizado en servidor cuando es posible para mejor SEO y performance
flashcards-gen/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API Routes (Backend for Frontend)
│ │ │ ├── generate-answer/ # Generación de respuestas IA
│ │ │ ├── getFlashcards/ # Obtener flashcards del usuario
│ │ │ ├── saveFlashcards/ # Guardar nuevas flashcards
│ │ │ ├── delete-flashcard/ # Eliminar flashcard
│ │ │ ├── user-data/ # Webhook de Clerk (crear usuario)
│ │ │ ├── onboarding-status/ # Estado del onboarding
│ │ │ ├── themes/ # CRUD de temas personalizados
│ │ │ └── dashboard/ # Estadísticas del dashboard
│ │ ├── dashboard/ # Dashboard protegido
│ │ │ ├── components/ # Componentes del dashboard
│ │ │ ├── flashcards/ # Vista de flashcards
│ │ │ ├── generate/ # Generador de flashcards
│ │ │ ├── hooks/ # Hooks del dashboard
│ │ │ │ ├── flashcards-query/
│ │ │ │ ├── themes-query/
│ │ │ │ ├── dashboard-stats/
│ │ │ │ └── user-sync/
│ │ │ └── user-profile/ # Perfil de usuario
│ │ ├── onboarding/ # Flujo de onboarding
│ │ │ ├── components/ # Pasos del onboarding
│ │ │ ├── register/
│ │ │ ├── verify/
│ │ │ ├── subscribe/
│ │ │ └── finished/
│ │ ├── pricing/ # Página de precios
│ │ ├── layout.tsx # Layout raíz
│ │ ├── page.tsx # Landing page
│ │ └── globals.css # Estilos globales
│ ├── components/ # Componentes reutilizables
│ │ ├── landing/ # Componentes de la landing
│ │ │ ├── Hero.tsx
│ │ │ ├── HowItWorks.tsx
│ │ │ ├── Pricing.tsx
│ │ │ ├── FAQ.tsx
│ │ │ └── Value.tsx
│ │ ├── ui/ # Componentes UI base (shadcn/ui)
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── sidebar.tsx
│ │ │ ├── app-sidebar.tsx # Sidebar animado del dashboard
│ │ │ ├── sync-indicator.tsx # Indicador de sincronización
│ │ │ └── ...
│ │ ├── fallbacks/ # Estados de carga
│ │ │ ├── LoadingModal.tsx
│ │ │ ├── SkeletonCard.tsx
│ │ │ └── subscription.tsx
│ │ └── provider/ # Providers de contexto
│ │ └── Provider.tsx # React Query Provider
│ ├── domain/ # Modelos de dominio
│ │ ├── flashcards.ts # Entidades de flashcards
│ │ └── themes.ts # Entidades de temas
│ ├── infrastructure/ # Capa de infraestructura
│ │ └── flashcardRepository.ts # Repositorio de flashcards
│ ├── store/ # Estado global (Zustand)
│ │ └── uiState/
│ │ └── uiState.ts # Estado de UI global
│ ├── hooks/ # Hooks personalizados globales
│ │ ├── use-mobile.ts # Detección de dispositivo móvil
│ │ └── useErrorMessage.ts # Manejo de mensajes de error
│ ├── animations/ # Sistema de animaciones
│ │ ├── utils.ts # Variantes de animación
│ │ ├── onboardingVariants.ts # Animaciones del onboarding
│ │ └── hooks/
│ │ └── useReducedMotion.ts # Hook de accesibilidad
│ ├── middleware/ # Middlewares
│ │ └── rate-limit.ts # Rate limiting con Upstash
│ ├── utils/ # Utilidades
│ │ ├── services/ # Servicios externos
│ │ │ ├── auth/ # Servicios de autenticación
│ │ │ ├── functions/ # Funciones de API
│ │ │ └── unitOfWork/ # Unit of Work pattern
│ │ ├── schemes/ # Esquemas de validación (Zod)
│ │ │ ├── flashcards-validation/
│ │ │ ├── form-question-validation/
│ │ │ └── get-answers-validation/
│ │ ├── consts/ # Constantes
│ │ │ └── ninjaModels.ts # Modelos de IA disponibles
│ │ └── fonts/ # Fuentes personalizadas
│ ├── lib/ # Librerías y configuraciones
│ │ ├── redis.ts # Cliente de Upstash Redis
│ │ ├── schema.ts # Schemas JSON-LD para SEO
│ │ └── utils.ts # Utilidades generales
│ └── types/ # Tipos TypeScript globales
│ └── global.d.ts
├── tests/ # Tests E2E con Playwright
│ └── landing.spec.ts
├── docs/ # Documentación adicional
│ ├── SIDEBAR_ANIMATIONS_IMPLEMENTATION.md
│ └── SIDEBAR_ANIMATIONS_TESTING.md
├── public/ # Recursos estáticos
├── .github/ # GitHub workflows y templates
│ └── instructions/
│ └── project-manual.instructions.md
├── next.config.ts # Configuración de Next.js
├── tsconfig.json # Configuración de TypeScript
├── playwright.config.ts # Configuración de Playwright
├── eslint.config.mjs # Configuración de ESLint
├── components.json # Configuración de shadcn/ui
├── pnpm-workspace.yaml # Configuración de pnpm
└── package.json # Dependencias y scripts
- Framework: Next.js 16.0.1 (App Router, React Server Components)
- UI Library: React 19.2.0
- Lenguaje: TypeScript 5.9.3
- Estilos: Tailwind CSS 4.1.16
- Componentes UI: shadcn/ui + Radix UI
- Animaciones: Motion (Framer Motion) 12.23.24
- Iconos: Lucide React 0.548.0
- Gráficos: Recharts 3.3.0
- Estado Global: Zustand 5.0.8
- Data Fetching: TanStack Query (React Query) 5.90.5
- Validación: Zod 4.1.12
- Runtime: Next.js API Routes (Node.js + Edge Runtime)
- Autenticación: Clerk 6.34.0
- Rate Limiting: Upstash Rate Limit 2.0.6
- Cache: Upstash Redis 1.35.6
- Package Manager: pnpm 10.20.0
- Linting: ESLint 9.38.0
- Testing E2E: Playwright 1.54.1
- Bundler: Turbopack (integrado en Next.js)
- Dev Tools: React Scan 0.4.3
- Schema.org: schema-dts 1.1.5
- Temas: next-themes 0.4.6
- Toasts: Sonner 2.0.7
- Node.js 18.x o superior
- pnpm 10.20.0 o superior
- Cuenta de Clerk (para autenticación)
- Cuenta de Upstash (para Redis y Rate Limiting)
git clone https://github.com/EleanQuintero/flashcards-gen.git
cd flashcards-genpnpm installCrea un archivo .env.local en la raíz del proyecto con las siguientes variables:
# Clerk Authentication
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
CLERK_SECRET_KEY=sk_test_xxxxx
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/onboarding
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
# Clerk Webhooks
CLERK_WEBHOOK_CREATE_USER_SIGNING_SECRET=whsec_xxxxx
CLERK_WEBHOOK_DELETE_USER_SIGNING_SECRET=whsec_xxxxx
# Upstash Redis (Rate Limiting)
UPSTASH_REDIS_REST_URL=https://xxxxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=xxxxx
# Backend API Endpoints (tu servidor de IA y datos)
SERVER_GENERATE_ANSWER=https://api.tubackend.com/generate-answer
SERVER_GET_FLASHCARDS_BY_USER=https://api.tubackend.com/flashcards
SERVER_SAVE_FLASHCARDS=https://api.tubackend.com/flashcards/save
SERVER_DELETE_FLASHCARD=https://api.tubackend.com/flashcards/delete/
SERVER_CREATE_USER=https://api.tubackend.com/users
SERVER_DELETE_USER_DATA=https://api.tubackend.com/users/
# Theme Endpoints
SERVER_CREATE_THEME=https://api.tubackend.com/themes/create
SERVER_GET_THEMES=https://api.tubackend.com/themes
SERVER_DELETE_THEME=https://api.tubackend.com/themes/delete
SERVER_UPDATE_THEME_STATUS=https://api.tubackend.com/themes/status
SERVER_GET_THEME_STATUS=https://api.tubackend.com/themes/statuspnpm devLa aplicación estará disponible en http://localhost:3000
# Desarrollo con Turbopack
pnpm dev
# Build de producción
pnpm build
# Iniciar servidor de producción
pnpm start
# Linting
pnpm lint
# Tests E2E
pnpm test:e2e
# Tests E2E con UI
pnpm test:e2e:ui| Endpoint | Método | Descripción | Rate Limit |
|---|---|---|---|
/api/user-data |
POST | Webhook de Clerk para crear usuario | N/A (webhook) |
/api/user-data/delete |
POST | Webhook de Clerk para eliminar usuario | N/A (webhook) |
/api/onboarding-status |
POST | Actualizar estado de onboarding | 3 req/60s |
| Endpoint | Método | Descripción | Rate Limit |
|---|---|---|---|
/api/getFlashcards |
GET | Obtener todas las flashcards del usuario | 20 req/60s |
/api/generate-answer |
POST | Generar respuesta IA para preguntas | 10 req/60s |
/api/saveFlashcards |
POST | Guardar nuevas flashcards | 10 req/60s |
/api/delete-flashcard |
DELETE | Eliminar una flashcard | 10 req/60s |
| Endpoint | Método | Descripción | Rate Limit |
|---|---|---|---|
/api/themes/get-themes-by-user |
GET | Obtener temas del usuario | 20 req/60s |
/api/themes/create-theme |
POST | Crear nuevo tema | 10 req/60s |
/api/themes/delete-theme |
DELETE | Eliminar tema | 10 req/60s |
/api/themes/get-theme-status |
GET | Obtener estado del tema | 20 req/60s |
/api/themes/update-status |
PUT | Actualizar estado del tema | 10 req/60s |
| Endpoint | Método | Descripción | Rate Limit |
|---|---|---|---|
/api/dashboard/count-flashcards-by-theme |
GET | Contar flashcards por tema | 20 req/60s |
/api/dashboard/latest-flashcards-created |
GET | Últimas flashcards creadas | 20 req/60s |
/api/dashboard/max-flashcards-by-user |
GET | Máximo de flashcards por usuario | 20 req/60s |
/api/dashboard/theme-with-most-flashcards |
GET | Tema con más flashcards | 20 req/60s |
El sistema implementa rate limiting granular con Upstash Redis:
// Configuraciones predefinidas
RATE_LIMIT_CONFIGS = {
DEFAULT: { requests: 5, duration: "60s" },
DASHBOARD: { requests: 20, duration: "60s" },
AUTH: { requests: 3, duration: "60s" },
READ: { requests: 20, duration: "60s" },
WRITE: { requests: 10, duration: "60s" }
}Cada endpoint tiene su propio identificador para evitar colisiones:
export const GET = rateLimitter({
fn: getFlashcards,
options: { ...RATE_LIMIT_CONFIGS.READ, identifier: 'getFlashcards' }
});Header: Barra de navegación con logo y botones de autenticaciónHero: Sección principal con propuesta de valor y CTAHowItWorks: Explicación del funcionamiento en 3 pasosValue: Beneficios y características destacadasPricing: Planes de suscripción (Free, Basic, Pro)FAQ: Preguntas frecuentesFooter: Enlaces legales y redes sociales
AppSidebar: Navegación lateral animada con Motion- Integración con Clerk (avatar de usuario)
- Rutas: Home, Flashcards, Generate
- Animaciones adaptativas según accesibilidad
Dashboard: Vista principal con estadísticas- Gráficos de progreso con Recharts
- Últimas flashcards creadas
- Distribución por temas
SyncIndicator: Indicador de estado de sincronización
GeneratorFlashCard: Formulario principal- Selector de modelo de IA
- Selector de tema
- Input de preguntas con validación
- Generación y previsualización de respuestas
ThemeSelector: Selector de temas con búsquedaThemeSetupModal: Modal para crear/editar temas
FlashcardGrid: Grid responsivo de flashcardsFlashcardCard: Tarjeta individual con efecto flipFlashcardFilters: Filtros por tema y fuente (modelo)
Start: Bienvenida al onboardingRegister: Formulario de registroVerify: Verificación de emailSubscribe: Selección de planFinish: Confirmación y redirección
Componentes reutilizables construidos sobre Radix UI:
- Formularios:
Button,Input,Textarea,Select - Feedback:
Dialog,Alert,Toast(Sonner) - Navegación:
Sidebar,Tabs,Sheet - Datos:
Card,Badge,Separator,Tooltip - Estados:
Skeleton,Progress,HoverCard
interface UIState {
error: string | null;
loading: boolean;
setError: (error: string | null) => void;
setLoading: (loading: boolean) => void;
}Uso: Estado global de carga y errores de la aplicación.
interface sourceState {
source: string; // "all" | "Basic" | "Pro"
setSource: (source: string) => void;
}Uso: Filtro de fuente de flashcards en el dashboard.
// Queries
- flashcards: Query<flashcard[]> // GET flashcards
- staleTime: 5 minutos
- gcTime: 30 minutos
- refetchOnWindowFocus: false
// Mutations
- getAnswer: Mutation<AnswerData> // Generar respuesta IA
- saveFlashcards: Mutation<void> // Guardar flashcards
- deleteFlashcard: Mutation<void> // Eliminar flashcard// Queries
- themes: Query<themes[]> // GET temas del usuario
- theme_status: Query<boolean> // GET estado de tema
// Mutations
- createTheme: Mutation<void> // Crear nuevo tema
- deleteTheme: Mutation<void> // Eliminar tema
- updateStatus: Mutation<void> // Actualizar estadoFlashcardUnitOfWork implementa el patrón Unit of Work para gestionar operaciones transaccionales:
class FlashcardUnitOfWork {
// Singleton instance
public static getInstance(): FlashcardUnitOfWork
// Operaciones
public async commitFlashcards(data: flashcardToSync): Promise<void>
public async loadUserFlashCards(): Promise<flashcard[]>
public async getAnswers(props: getAnswersProps): Promise<AnswerData>
public async deleteFlashcard(id: string): Promise<void>
}Beneficios:
- Centralización de lógica de negocio
- Transaccionalidad implícita
- Fácil testing y mocking
- Separación clara entre capas
MemoryNinja implementa un sistema de animaciones accesible con Motion (Framer Motion):
- Lazy Loading: Solo carga animaciones cuando son necesarias
- Accesibilidad: Detecta
prefers-reduced-motionautomáticamente - Performance: Usa
transformyopacitypara animaciones de 60fps - Variants: Sistema de variantes reutilizables
// src/animations/hooks/useReducedMotion.ts
export const useReducedMotion = () => {
const [shouldReduceMotion, setShouldReduceMotion] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
setShouldReduceMotion(mediaQuery.matches);
const handleChange = () => setShouldReduceMotion(mediaQuery.matches);
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);
return shouldReduceMotion;
};// src/animations/utils.ts
export const sidebarMenuItemVariants = {
hidden: { opacity: 0, x: -20 },
visible: (i: number) => ({
opacity: 1,
x: 0,
transition: {
delay: i * 0.1,
duration: 0.3,
ease: "easeOut",
},
}),
};
export const sidebarIconVariants = {
idle: { scale: 1 },
hover: { scale: 1.1, transition: { duration: 0.2 } },
tap: { scale: 0.95 },
};// src/components/ui/page-transition.tsx
<LazyMotion features={domAnimation}>
<m.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</m.div>
</LazyMotion>Características implementadas:
- ✅ Sign In / Sign Up con email y contraseña
- ✅ Verificación de email obligatoria
- ✅ Localización en español (
esES) - ✅ Protección de rutas con
<Protect> - ✅ Webhooks para sincronización de usuarios
- ✅ Metadata personalizada (onboarding, subscripción)
Dashboard protegido:
<Protect
condition={(has) => has({ feature: "pro_user" }) || Boolean(isAdmin)}
fallback={<SubscriptionFallback />}
>
<Dashboard />
</Protect>Sistema multinivel con Upstash:
- Rate limiting por usuario (no por IP)
- Identificadores únicos por endpoint
- Headers informativos en respuestas:
X-RateLimit-Limit: Límite máximoX-RateLimit-Remaining: Requests restantesX-RateLimit-Reset: Timestamp de resetRetry-After: Segundos hasta poder reintentar
Ejemplo de configuración:
export const POST = rateLimitter({
fn: generateAnswer,
options: {
requests: 10, // 10 requests
duration: "60s", // por 60 segundos
identifier: 'generateAnswer' // identificador único
}
});Schemas de validación:
// Flashcards
const flashcardSchema = z.object({
flashcard: z.array(
z.object({
question: z.string().min(1).max(500),
answer: z.string().min(1).max(290),
theme: z.string()
})
).min(1, "Debe tener al menos una flashcard"),
user_id: z.string()
});
// Generación de respuestas
const getAnswersSchema = z.object({
questions: z.array(z.string()).min(1),
theme: z.string().min(1),
model: z.string().min(1)
});Verificación de firma Clerk:
const evt = await verifyWebhook(req, {
signingSecret: process.env.CLERK_WEBHOOK_CREATE_USER_SIGNING_SECRET!
});
if (evt.type !== "user.created") {
return NextResponse.json({ received: true }, { status: 200 });
}Configuración (playwright.config.ts):
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
],
});Suite de Tests de Landing Page:
- ✅ Renderizado de componentes principales
- ✅ Navegación y enlaces funcionales
- ✅ Responsividad (móvil, tablet, desktop)
- ✅ SEO (metadatos, títulos, descripciones)
- ✅ Accesibilidad (roles ARIA)
Ejecutar tests:
# Todos los tests
pnpm test:e2e
# Con interfaz gráfica
pnpm test:e2e:ui
# En un navegador específico
pnpm test:e2e --project=chromium
# Ver reporte
pnpm dlx playwright show-reportJSON-LD Schemas:
// Organization Schema
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Memory Ninja",
"url": "https://www.memoryninja.es",
"logo": "https://res.cloudinary.com/...",
"sameAs": ["twitter.com/...", "instagram.com/..."]
}
// Website Schema con SearchAction
{
"@context": "https://schema.org",
"@type": "WebSite",
"potentialAction": {
"@type": "SearchAction",
"target": "https://memoryninja.es/search?q={search_term_string}"
}
}Metadatos dinámicos:
- Títulos únicos por página
- Descripciones optimizadas
- Open Graph para redes sociales
- Keywords relevantes
Optimizaciones implementadas:
- ✅ Turbopack: Bundler ultra-rápido de Next.js
- ✅ Server Components: Renderizado en servidor por defecto
- ✅ Image Optimization: Deshabilitado por configuración (imágenes estáticas)
- ✅ Code Splitting: Automático por ruta
- ✅ Lazy Loading: Componentes y animaciones
- ✅ React Query Cache: Reducción de llamadas redundantes
- ✅ Debounce: En inputs de búsqueda y generación
Configuración de caché:
// Flashcards
staleTime: 5 * 60 * 1000, // 5 minutos
gcTime: 30 * 60 * 1000, // 30 minutos
// Themes
staleTime: 60 * 60 * 1000, // 60 minutos
gcTime: 60 * 60 * 1000, // 60 minutosSistema de gestión de errores:
// Hook personalizado
const { errorMessage, setError, clearError } = useErrorMessage();
// En mutations
onError: (error: Error) => {
toast.error(error.message);
setError(error.message);
}
// En queries
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)Estados de carga:
- Skeleton screens
- Loading modals
- Progress indicators
- Toasts informativos
Implementaciones:
- ✅ Semantic HTML
- ✅ ARIA roles y labels
- ✅ Navegación por teclado
- ✅ Focus visible
- ✅
prefers-reduced-motion - ✅ Contraste de colores (WCAG AA)
- ✅ Textos alternativos en imágenes
Preparado para i18n:
- Clerk con localización española (
esES) - Mensajes de error en español
- Estructura preparada para añadir más idiomas
1. Conecta tu repositorio:
# Instala Vercel CLI
pnpm add -g vercel
# Deploy
vercel2. Configura variables de entorno en el dashboard de Vercel:
- Añade todas las variables del
.env.local - Marca las variables sensibles como "Sensitive"
3. Configuración de dominio:
- Conecta tu dominio personalizado
- Configura DNS (A record o CNAME)
- SSL automático con Let's Encrypt
Netlify:
# netlify.toml
[build]
command = "pnpm build"
publish = ".next"
[[plugins]]
package = "@netlify/plugin-nextjs"Railway / Render:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]# ✅ Clerk (Obligatorio)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
CLERK_SECRET_KEY
CLERK_WEBHOOK_CREATE_USER_SIGNING_SECRET
CLERK_WEBHOOK_DELETE_USER_SIGNING_SECRET
# ✅ Upstash Redis (Obligatorio)
UPSTASH_REDIS_REST_URL
UPSTASH_REDIS_REST_TOKEN
# ✅ Backend APIs (Obligatorio)
SERVER_GENERATE_ANSWER
SERVER_GET_FLASHCARDS_BY_USER
SERVER_SAVE_FLASHCARDS
SERVER_DELETE_FLASHCARD
SERVER_CREATE_USER
SERVER_DELETE_USER_DATA¡Las contribuciones son bienvenidas! Sigue estos pasos:
git clone https://github.com/EleanQuintero/flashcards-gen.git
cd flashcards-gen
git remote add upstream https://github.com/EleanQuintero/flashcards-gen.gitgit checkout -b feature/nueva-funcionalidad
# o
git checkout -b fix/correccion-bug- Sigue el estilo de código existente
- Añade tests si es necesario
- Actualiza documentación
git commit -m "feat: añade sistema de notificaciones push"
# o
git commit -m "fix: corrige bug en generación de flashcards"Formato de commits (Conventional Commits):
feat:Nueva funcionalidadfix:Corrección de bugdocs:Cambios en documentaciónstyle:Formateo, punto y coma faltantes, etc.refactor:Refactorización de códigotest:Añade o modifica testschore:Tareas de mantenimiento
git push origin feature/nueva-funcionalidadAbre un Pull Request en GitHub con:
- Descripción clara de los cambios
- Screenshots si aplica
- Referencias a issues relacionados
- Sé respetuoso y constructivo
- Acepta críticas constructivas
- Enfócate en lo mejor para la comunidad
Este proyecto está licenciado bajo la Licencia MIT.
MIT License
Copyright (c) 2025 Elean Quintero
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.
- Autor: Elean Quintero
- GitHub: @EleanQuintero
- Email: [email protected]
- Issues: GitHub Issues
- Next.js - Framework React increíble
- Clerk - Autenticación sin complicaciones
- shadcn/ui - Componentes UI hermosos
- Upstash - Redis serverless
- Vercel - Hosting y despliegue
- Comunidad open-source ❤️
⭐ Si te gusta el proyecto, dale una estrella en GitHub