- | {user.name} |
+ {user.name || (user.first_name ? `${user.first_name} ${user.last_name || ''}` : 'Sin nombre')} |
{user.email} |
-
+
{user.role}
|
-
- {user.status === 'active' ? 'Activo' : 'Inactivo'}
+
+ {(user.status === 'active' || user.active) ? 'Activo' : 'Inactivo'}
|
diff --git a/src/v2/pages/V2AdminUsers.test.jsx b/src/v2/pages/V2AdminUsers.test.jsx
index 07d6e21..8a73d01 100644
--- a/src/v2/pages/V2AdminUsers.test.jsx
+++ b/src/v2/pages/V2AdminUsers.test.jsx
@@ -2,9 +2,38 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import V2AdminUsers from './V2AdminUsers';
+import UserService from '../../services/UserService';
+
+vi.mock('../../services/UserService');
describe('V2AdminUsers', () => {
- it('renders user list after loading', async () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders user list correctly', async () => {
+ UserService.getUsers.mockResolvedValue({
+ data: [
+ { id: 1, name: 'Admin User', email: 'admin@test.com', role: 'Admin', status: 'active' },
+ { id: 2, name: 'Premium User', email: 'premium@test.com', role: 'Premium', status: 'active' }
+ ]
+ });
+
+ render(
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('Admin User')).toBeTruthy();
+ expect(screen.getByText('premium@test.com')).toBeTruthy();
+ });
+ });
+
+ it('handles empty user list', async () => {
+ UserService.getUsers.mockResolvedValue({ data: [] });
+
render(
@@ -12,9 +41,7 @@ describe('V2AdminUsers', () => {
);
await waitFor(() => {
- expect(screen.getByText('Juan Pérez')).toBeTruthy();
- expect(screen.getByText('maria@example.com')).toBeTruthy();
- expect(screen.getByText('Admin')).toBeTruthy();
- }, { timeout: 2000 });
+ expect(screen.getByText(/No hay usuarios registrados/i)).toBeTruthy();
+ });
});
});
diff --git a/src/v2/pages/V2CouponCenter.jsx b/src/v2/pages/V2CouponCenter.jsx
index 79d2035..10fe313 100644
--- a/src/v2/pages/V2CouponCenter.jsx
+++ b/src/v2/pages/V2CouponCenter.jsx
@@ -1,27 +1,33 @@
import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
+import CouponService from '../../services/CouponService';
import '../styles/v2-theme.css';
const V2CouponCenter = () => {
const history = useHistory();
const [coupons, setCoupons] = useState([]);
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
useEffect(() => {
- // Mocking API call to /v2/coupons/me
- setTimeout(() => {
- setCoupons([
- { id: 'c1', code: 'BIENVENIDO25', discount: '25%', description: 'Descuento de bienvenida', expires: '2025-12-31', status: 'active' },
- { id: 'c2', code: 'STUDENTLIFE', discount: '50%', description: 'Descuento para estudiantes', expires: '2025-06-30', status: 'active' },
- { id: 'c3', code: 'EXPIRED10', discount: '10%', description: 'Promoción antigua', expires: '2024-01-01', status: 'expired' }
- ]);
- setLoading(false);
- }, 800);
+ const fetchCoupons = async () => {
+ try {
+ const response = await CouponService.getCoupons();
+ setCoupons(response.data || []);
+ } catch (err) {
+ console.error("Error fetching coupons:", err);
+ setError("No se pudieron cargar los cupones. Por favor, intenta de nuevo más tarde.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchCoupons();
}, []);
const copyToClipboard = (code) => {
navigator.clipboard.writeText(code);
- // Show some feedback here if needed, like a toast
+ // Toast logic could be added here if a global toast exists
};
return (
@@ -43,6 +49,16 @@ const V2CouponCenter = () => {
+ ) : error ? (
+
+ error_outline
+ {error}
+
+ ) : coupons.length === 0 ? (
+
+ confirmation_number
+ No tienes cupones disponibles en este momento.
+
) : (
{coupons.map(coupon => (
diff --git a/src/v2/pages/V2CouponCenter.test.jsx b/src/v2/pages/V2CouponCenter.test.jsx
index 992f4b1..713c6ed 100644
--- a/src/v2/pages/V2CouponCenter.test.jsx
+++ b/src/v2/pages/V2CouponCenter.test.jsx
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import V2CouponCenter from './V2CouponCenter';
+import CouponService from '../../services/CouponService';
const mockGoBack = vi.fn();
vi.mock('react-router-dom', async () => {
@@ -14,22 +15,30 @@ vi.mock('react-router-dom', async () => {
};
});
+vi.mock('../../services/CouponService');
+
describe('V2CouponCenter', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders loading state initially', () => {
+ CouponService.getCoupons.mockReturnValue(new Promise(() => {}));
render(
);
- // Find by class instead of role since materialize preloader might not have role progressbar by default
expect(document.querySelector('.preloader-wrapper')).toBeTruthy();
});
it('renders coupons after loading', async () => {
+ CouponService.getCoupons.mockResolvedValue({
+ data: [
+ { id: 'c1', code: 'PROMO2025', discount: '20%', description: 'Test Coupon', expires: '2025-12-31', status: 'active' }
+ ]
+ });
+
render(
@@ -37,9 +46,9 @@ describe('V2CouponCenter', () => {
);
await waitFor(() => {
- expect(screen.getByText('BIENVENIDO25')).toBeTruthy();
- expect(screen.getByText('STUDENTLIFE')).toBeTruthy();
- }, { timeout: 2000 });
+ expect(screen.getByText('PROMO2025')).toBeTruthy();
+ expect(screen.getByText('20% OFF')).toBeTruthy();
+ });
});
it('handles copy to clipboard', async () => {
@@ -48,30 +57,21 @@ describe('V2CouponCenter', () => {
};
Object.assign(navigator, { clipboard: mockClipboard });
- render(
-
-
-
- );
-
- await waitFor(() => screen.getByText('BIENVENIDO25'));
-
- const copyButtons = screen.getAllByText('Copiar Código');
- fireEvent.click(copyButtons[0]);
+ CouponService.getCoupons.mockResolvedValue({
+ data: [{ id: 'c1', code: 'PROMO2025', discount: '20%', description: 'Test Coupon', expires: '2025-12-31', status: 'active' }]
+ });
- expect(mockClipboard.writeText).toHaveBeenCalledWith('BIENVENIDO25');
- });
-
- it('disables copy button for expired coupons', async () => {
render(
);
- await waitFor(() => screen.getByText('EXPIRED10'));
+ await waitFor(() => screen.getByText('PROMO2025'));
+
+ const copyButton = screen.getByText('Copiar Código');
+ fireEvent.click(copyButton);
- const expiredButton = screen.getAllByRole('button', { name: /Copiar Código/i }).find(btn => btn.disabled);
- expect(expiredButton).toBeTruthy();
+ expect(mockClipboard.writeText).toHaveBeenCalledWith('PROMO2025');
});
});
diff --git a/src/v2/pages/V2FlashcardCreator.jsx b/src/v2/pages/V2FlashcardCreator.jsx
index 2f66a64..f3250e3 100644
--- a/src/v2/pages/V2FlashcardCreator.jsx
+++ b/src/v2/pages/V2FlashcardCreator.jsx
@@ -1,24 +1,67 @@
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
+import FlashcardService from '../../services/FlashcardService';
+import ExamService from '../../services/ExamService';
import '../styles/v2-theme.css';
const V2FlashcardCreator = () => {
const history = useHistory();
const [front, setFront] = useState('');
const [back, setBack] = useState('');
- const [specialty, setSpecialty] = useState('');
+ const [specialtyId, setSpecialtyId] = useState('');
+ const [specialties, setSpecialties] = useState([]);
const [saving, setSaving] = useState(false);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchSpecialties = async () => {
+ try {
+ const response = await ExamService.loadCategories();
+ setSpecialties(response.data || []);
+ } catch (err) {
+ console.error("Error fetching specialties:", err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchSpecialties();
+ }, []);
const handleSave = async (e) => {
e.preventDefault();
+ if (!specialtyId || !front || !back) return;
+
setSaving(true);
- // Mocking API call to POST /v2/flashcards
- setTimeout(() => {
+ try {
+ await FlashcardService.createFlashcard({
+ front,
+ back,
+ specialty_id: specialtyId
+ });
+ history.push('/v2/flashcards/repaso');
+ } catch (err) {
+ console.error("Error saving flashcard:", err);
+ alert("Ocurrió un error al guardar la flashcard. Por favor, intenta de nuevo.");
+ } finally {
setSaving(false);
- history.push('/v2/repaso');
- }, 1000);
+ }
};
+ if (loading) {
+ return (
+
+ );
+ }
+
return (
@@ -34,16 +77,15 @@ const V2FlashcardCreator = () => {
@@ -57,6 +99,7 @@ const V2FlashcardCreator = () => {
value={front}
onChange={(e) => setFront(e.target.value)}
required
+ style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid var(--md-sys-color-outline-variant)', backgroundColor: 'transparent', color: 'inherit' }}
/>
@@ -70,6 +113,7 @@ const V2FlashcardCreator = () => {
value={back}
onChange={(e) => setBack(e.target.value)}
required
+ style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid var(--md-sys-color-outline-variant)', backgroundColor: 'transparent', color: 'inherit' }}
/>
diff --git a/src/v2/pages/V2FlashcardCreator.test.jsx b/src/v2/pages/V2FlashcardCreator.test.jsx
index b64735b..feb55b1 100644
--- a/src/v2/pages/V2FlashcardCreator.test.jsx
+++ b/src/v2/pages/V2FlashcardCreator.test.jsx
@@ -2,9 +2,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import V2FlashcardCreator from './V2FlashcardCreator';
+import FlashcardService from '../../services/FlashcardService';
+import ExamService from '../../services/ExamService';
const mockPush = vi.fn();
const mockGoBack = vi.fn();
+
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
return {
@@ -16,40 +19,54 @@ vi.mock('react-router-dom', async () => {
};
});
+vi.mock('../../services/FlashcardService');
+vi.mock('../../services/ExamService');
+
describe('V2FlashcardCreator', () => {
beforeEach(() => {
vi.clearAllMocks();
+ ExamService.loadCategories.mockResolvedValue({
+ data: [{ id: 1, name: 'Pediatría' }]
+ });
});
- it('renders form elements correctly', () => {
+ it('renders correctly after loading specialties', async () => {
render(
);
- expect(screen.getByLabelText(/Especialidad/i)).toBeTruthy();
- expect(screen.getByLabelText(/Anverso/i)).toBeTruthy();
- expect(screen.getByLabelText(/Reverso/i)).toBeTruthy();
+ await waitFor(() => {
+ expect(screen.getByText('Crear Flashcard')).toBeTruthy();
+ expect(screen.getByText('Pediatría')).toBeTruthy();
+ });
});
it('handles form submission', async () => {
+ FlashcardService.createFlashcard.mockResolvedValue({ data: { success: true } });
+
render(
);
- fireEvent.change(screen.getByLabelText(/Especialidad/i), { target: { value: 'pediatria' } });
- fireEvent.change(screen.getByLabelText(/Anverso/i), { target: { value: 'Pregunta' } });
- fireEvent.change(screen.getByLabelText(/Reverso/i), { target: { value: 'Respuesta' } });
+ await waitFor(() => screen.getByText('Pediatría'));
- fireEvent.click(screen.getByText('Guardar Flashcard'));
+ fireEvent.change(screen.getByLabelText(/Especialidad/i), { target: { value: '1' } });
+ fireEvent.change(screen.getByPlaceholderText(/Escribe la pregunta/i), { target: { value: 'Pregunta de prueba' } });
+ fireEvent.change(screen.getByPlaceholderText(/Escribe la respuesta/i), { target: { value: 'Respuesta de prueba' } });
- expect(screen.getByText('Guardando...')).toBeTruthy();
+ fireEvent.click(screen.getByText('Guardar Flashcard'));
await waitFor(() => {
- expect(mockPush).toHaveBeenCalledWith('/v2/repaso');
- }, { timeout: 2000 });
+ expect(FlashcardService.createFlashcard).toHaveBeenCalledWith({
+ front: 'Pregunta de prueba',
+ back: 'Respuesta de prueba',
+ specialty_id: '1'
+ });
+ expect(mockPush).toHaveBeenCalledWith('/v2/flashcards/repaso');
+ });
});
});
|