Crea una App de Flashcards con Next.js y MongoDB

Crea una App de Flashcards con Next.js y MongoDB

Crea una App de Flashcards con Next.js y MongoDB

Si alguna vez te has pasado la noche antes de un examen intentando memorizarlo todo, sabes lo difícil que es retener la información. Las flashcards son una de las herramientas de estudio más efectivas porque usan **recuerdo activo** — obligan a tu cerebro a recuperar la información en lugar de simplemente releerla pasivamente.

Construir una app digital de flashcards combina desarrollo web moderno con psicología educativa. En esta guía te llevo paso a paso para crear una aplicación full-stack usando Next.js para frontend y backend, y MongoDB para almacenar los datos.

¿Por Qué Construir una App de Flashcards?

Las flashcards digitales tienen varias ventajas sobre las físicas:

- **Portabilidad**: Accedes a tus mazos desde cualquier sitio con internet
- **Repetición espaciada**: Algoritmos que optimizan cuándo repasar cada tarjeta
- **Soporte multimedia**: Imágenes, audio, formato rico
- **Seguimiento de progreso**: Analíticas sobre tu rendimiento

Casos de Uso Reales

Antes de meternos en código, piensa en escenarios donde esta app brilla:

1. **Estudiantes de medicina**: Creando flashcards detalladas de anatomía con imágenes
2. **Aprendices de idiomas**: Mazos de vocabulario con audio de pronunciación
3. **Certificaciones profesionales**: Organizando conceptos técnicos complejos de forma sistemática

Stack Tecnológico

Vamos a usar:

- **Next.js 14** (framework React con renderizado del lado del servidor)
- **MongoDB Atlas** (base de datos en la nube)
- **Mongoose** (modelado de objetos para MongoDB)
- **Tailwind CSS** (estilos)

¿Por qué este stack? Next.js maneja tanto el frontend como las rutas API, simplificando el despliegue. El esquema flexible de MongoDB encaja bien con estructuras variables de flashcards.

Configuración del Proyecto

Primero, crea un nuevo proyecto Next.js:

```bash
npx create-next-app@latest flashcard-app
cd flashcard-app
npm install mongoose mongodb
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

Configura Tailwind en `tailwind.config.js`:

```javascript
/** @type {import('tailwindcss').tailwind.config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,jsx}",
"./pages/**/*.{js,jsx,ts,jsx}",
"./components/**/*.{js,jsx,ts,jsx}",
],
theme: {
extend: {},
},
plugins: [],
}
```

Añade a `app/globals.css`:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Conexión a la Base de Datos

Crea `lib/mongodb.js`:

```javascript
import { MongoClient } from 'mongodb'

const uri = process.env.MONGODB_URI
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
}

let client
let clientPromise

if (!process.env.MONGODB_URI) {
throw new Error('Añade tu URI de MongoDB a .env.local')
}

if (process.env.NODE_ENV === 'development') {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
client = new MongoClient(uri, options)
clientPromise = client.connect()
}

export default clientPromise
```

Modelo de Flashcard

En `models/Flashcard.js`:

```javascript
import mongoose from 'mongoose'

const flashcardSchema = new mongoose.Schema({
question: {
type: String,
required: true,
},
answer: {
type: String,
required: true,
},
deck: {
type: String,
required: true,
},
tags: [String],
createdAt: {
type: Date,
default: Date.now,
},
})

export default mongoose.models.Flashcard || mongoose.model('Flashcard', flashcardSchema)
```

Rutas API

Las rutas API de Next.js manejan nuestra lógica de backend. Crea `pages/api/flashcards/index.js`:

```javascript
import dbConnect from '../../../lib/dbConnect'
import Flashcard from '../../../models/Flashcard'

export default async function handler(req, res) {
const { method } = req

await dbConnect()

switch (method) {
case 'GET':
try {
const flashcards = await Flashcard.find({})
res.status(200).json(flashcards)
} catch (error) {
res.status(400).json({ success: false })
}
break
case 'POST':
try {
const flashcard = await Flashcard.create(req.body)
res.status(201).json(flashcard)
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
}
```

Componentes del Frontend

Crea el componente de formulario `components/FlashcardForm.js`:

```javascript
import { useState } from 'react'

export default function FlashcardForm({ onSubmit }) {
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const [deck, setDeck] = useState('')

const handleSubmit = async (e) => {
e.preventDefault()
const response = await fetch('/api/flashcards', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, answer, deck })
})

if (response.ok) {
const newFlashcard = await response.json()
onSubmit(newFlashcard)
setQuestion('')
setAnswer('')
setDeck('')
}
}

return (



value={question}
onChange={(e) => setQuestion(e.target.value)}
required
className="w-full p-2 border rounded"
/>



value={answer}
onChange={(e) => setAnswer(e.target.value)}
required
className="w-full p-2 border rounded"
/>



type="text"
value={deck}
onChange={(e) => setDeck(e.target.value)}
required
className="w-full p-2 border rounded"
/>



)
}
```

Interfaz de Estudio

Crea `components/StudyMode.js` para practicar recuerdo activo:

```javascript
import { useState, useEffect } from 'react'

export default function StudyMode({ flashcards }) {
const [currentIndex, setCurrentIndex] = useState(0)
const [showAnswer, setShowAnswer] = useState(false)
const [shuffledCards, setShuffledCards] = useState([])

useEffect(() => {
setShuffledCards([...flashcards].sort(() => Math.random() - 0.5))
}, [flashcards])

const nextCard = () => {
setShowAnswer(false)
if (currentIndex < shuffledCards.length - 1) {
setCurrentIndex(currentIndex + 1)
}
}

const prevCard = () => {
setShowAnswer(false)
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1)
}
}

if (!shuffledCards.length) return

No hay flashcards disponibles



const card = shuffledCards[currentIndex]

return (


Pregunta:


{card.question}




{showAnswer && (

Respuesta:


{card.answer}



)}





{currentIndex > 0 && (

)}
{currentIndex < shuffledCards.length - 1 && (

)}




Tarjeta {currentIndex + 1} de {shuffledCards.length}



)
}
```

Página Principal de la Aplicación

Crea `app/page.js`:

```javascript
import { useState, useEffect } from 'react'
import FlashcardForm from '../components/FlashcardForm'
import StudyMode from '../components/StudyMode'

export default function Home() {
const [flashcards, setFlashcards] = useState([])
const [activeView, setActiveView] = useState('create')
const [selectedDeck, setSelectedDeck] = useState('')

useEffect(() => {
fetchFlashcards()
}, [])

const fetchFlashcards = async () => {
const response = await fetch('/api/flashcards')
const data = await response.json()
setFlashcards(data)
}

const addFlashcard = (newFlashcard) => {
setFlashcards([newFlashcard, ...flashcards])
}

const filteredCards = selectedDeck
? flashcards.filter(card => card.deck === selectedDeck)
: flashcards

const decks = [...new Set(flashcards.map(card => card.deck))]

return (


App de Estudio con Flashcards




onClick={() => setActiveView('create')}
className={`px-4 py-2 mr-2 rounded ${
activeView === 'create' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
>
Crear Flashcards

onClick={() => setActiveView('study')}
className={`px-4 py-2 rounded ${
activeView === 'study' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
disabled={flashcards.length === 0}
>
Modo Estudio



{activeView === 'create' && (



)}

{activeView === 'study' && (


value={selectedDeck}
onChange={(e) => setSelectedDeck(e.target.value)}
className="w-full p-2 border rounded"
>

{decks.map(deck => (

))}


)}

{activeView === 'study' && }


Tus Mazos


{decks.length === 0 ? (

Todavía no hay mazos. ¡Crea tu primera flashcard arriba!


) : (

    {decks.map(deck => (

  • {deck} ({flashcards.filter(c => c.deck === deck).length} tarjetas)

  • ))}

)}



)
}
```

Consideraciones para Despliegue

Para producción, piensa en estas mejoras:

1. **Autenticación**: Añade cuentas de usuario con NextAuth.js
2. **Repetición Espaciada**: Implementa algoritmos como SM-2
3. **Medios Enriquecidos**: Soporte para imágenes y audio en flashcards
4. **Soporte Offline**: Service workers para capacidades PWA

Despliega en Vercel (optimizado para Next.js) o plataforma similar. Configura clúster en MongoDB Atlas y variables de entorno.

Conclusión

Esta app de flashcards demuestra conceptos fundamentales sin dejar de ser práctica. Ahora tienes una base que puedes extender según tus necesidades. Ya sea para estudiar idiomas, preparar exámenes o memorizar material profesional, las flashcards digitales ofrecen una solución efectiva y personalizable.

Empieza simple, itera seguido y céntrate en la experiencia de usuario core. ¡Mucha suerte en tu camino de aprendizaje!

Preguntas Frecuentes

P: ¿Necesito experiencia previa con MongoDB?
R: Ayuda tener nociones básicas pero no es obligatorio. El modelo de documentos de MongoDB es intuitivo una vez entiendes las estructuras tipo JSON.

P: ¿Puedo añadir imágenes a las flashcards?
R: ¡Claro! Extiende el esquema con URLs de imágenes y modifica los componentes del formulario para manejar subida de archivos.

P: ¿Cómo implemento repetición espaciada?
R: Guarda historial de revisiones con timestamps, luego calcula intervalos óptimos según el rendimiento de recuerdo. Librerías como `supermemo` ayudan con los algoritmos.

P: ¿Funciona bien en móviles?
R: Con frameworks CSS responsivos como Tailwind, sí. Considera añadir características PWA para experiencia nativa.

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment