import React, { useState, useEffect, useLayoutEffect, useRef } from 'react'; import { getCurrentUser } from '../api/users.ts'; import { useNotification } from '../context/NotificationContext'; import { fetchWithAuth, patchWithAuth, postWithAuth, postFormDataWithAuth } from '../fetchWithAuth'; // Animación fade-in/slide-up para el componente const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`; if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-settings')) { const style = document.createElement('style'); style.id = 'fadein-slideup-settings'; style.innerHTML = fadeInSlideUp; document.head.appendChild(style); } const Settings = () => { const [activeTab, setActiveTab] = useState('profile'); const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const { showMessage } = useNotification(); // Estados para el formulario de perfil const [formData, setFormData] = useState({ first_name: '', last_name: '', email: '', username: '', rfc: '' }); // Estados para cambio de contraseña const [passwordData, setPasswordData] = useState({ current_password: '', new_password: '', confirm_password: '' }); const [passwordErrors, setPasswordErrors] = useState({}); const [changingPassword, setChangingPassword] = useState(false); // Estado para controlar la animación de entrada const [showAnimation, setShowAnimation] = useState(false); const [hasAnimated, setHasAnimated] = useState(false); // Ref para el input de archivo const fileInputRef = useRef(null); useLayoutEffect(() => { // Forzar un render antes de activar la animación setShowAnimation(true); }, []); useEffect(() => { if (showAnimation && !hasAnimated) { const timeout = setTimeout(() => { setHasAnimated(true); setShowAnimation(false); }, 700); // Duración igual a la animación return () => clearTimeout(timeout); } }, [showAnimation, hasAnimated]); // Cargar información del usuario al montar el componente useEffect(() => { const loadUserData = async () => { try { const token = localStorage.getItem('access'); if (token) { const userData = await getCurrentUser(token); setCurrentUser(userData); // Inicializar formData con los datos del usuario setFormData({ first_name: userData.first_name || '', last_name: userData.last_name || '', email: userData.email || '', username: userData.username || '', rfc: userData.rfc || '' }); } } catch (error) { console.error('Error al cargar datos del usuario:', error); showMessage('Error al cargar los datos del usuario', 'error'); } finally { setLoading(false); } }; loadUserData(); }, [showMessage]); // Función para actualizar el usuario const updateUser = async (userData) => { const API_URL = import.meta.env.VITE_EFC_API_URL; try { const response = await patchWithAuth(`${API_URL}/user/users/${currentUser.id}/`, userData); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Error al actualizar el usuario'); } return await response.json(); } catch (error) { console.error('Error updating user:', error); throw error; } }; // Manejar cambios en el formulario const handleInputChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; // Manejar cambios en el formulario de contraseña const handlePasswordChange = (e) => { const { name, value } = e.target; setPasswordData(prev => ({ ...prev, [name]: value })); // Limpiar errores cuando el usuario empiece a escribir if (passwordErrors[name]) { setPasswordErrors(prev => ({ ...prev, [name]: '' })); } }; // Validar contraseña const validatePassword = (password) => { const errors = []; if (password.length < 8) { errors.push('Debe tener al menos 8 caracteres'); } if (!/(?=.*[a-z])/.test(password)) { errors.push('Debe contener al menos una letra minúscula'); } if (!/(?=.*[A-Z])/.test(password)) { errors.push('Debe contener al menos una letra mayúscula'); } if (!/(?=.*\d)/.test(password)) { errors.push('Debe contener al menos un número'); } return errors; }; // Cambiar contraseña const handleChangePassword = async () => { const errors = {}; // Validaciones if (!passwordData.current_password.trim()) { errors.current_password = 'La contraseña actual es requerida'; } if (!passwordData.new_password.trim()) { errors.new_password = 'La nueva contraseña es requerida'; } else { const passwordValidationErrors = validatePassword(passwordData.new_password); if (passwordValidationErrors.length > 0) { errors.new_password = passwordValidationErrors[0]; // Mostrar el primer error } } if (!passwordData.confirm_password.trim()) { errors.confirm_password = 'Confirma tu nueva contraseña'; } else if (passwordData.new_password !== passwordData.confirm_password) { errors.confirm_password = 'Las contraseñas no coinciden'; } if (Object.keys(errors).length > 0) { setPasswordErrors(errors); return; } setChangingPassword(true); try { const API_URL = import.meta.env.VITE_EFC_API_URL; const response = await postWithAuth(`${API_URL}/user/users/${currentUser.id}/change_password/`, { old_password: passwordData.current_password, new_password: passwordData.new_password }); if (!response.ok) { const errorData = await response.json(); if (errorData.old_password) { setPasswordErrors({ current_password: 'La contraseña actual es incorrecta' }); } else { throw new Error(errorData.detail || 'Error al cambiar la contraseña'); } return; } // Limpiar formulario y mostrar mensaje de éxito setPasswordData({ current_password: '', new_password: '', confirm_password: '' }); setPasswordErrors({}); showMessage('Contraseña cambiada exitosamente', 'success'); } catch (error) { console.error('Error changing password:', error); showMessage(error.message || 'Error al cambiar la contraseña', 'error'); } finally { setChangingPassword(false); } }; // Guardar cambios del perfil const handleSaveProfile = async () => { if (!currentUser) return; // Validaciones básicas if (!formData.first_name.trim()) { showMessage('El nombre es requerido', 'error'); return; } if (!formData.last_name.trim()) { showMessage('El apellido es requerido', 'error'); return; } if (!formData.email.trim()) { showMessage('El email es requerido', 'error'); return; } // Username validation removed - field is now read-only // Validar formato de email const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(formData.email)) { showMessage('Por favor ingresa un email válido', 'error'); return; } setSaving(true); try { // Preparar datos para enviar - NO incluir grupos ni RFC para preservarlos const updateData = { first_name: formData.first_name.trim(), last_name: formData.last_name.trim(), email: formData.email.trim(), // Username NO se incluye - no se puede modificar // RFC NO se incluye - no se puede modificar is_importador: currentUser.is_importador, // Preservar estado actual is_active: currentUser.is_active // Preservar estado actual }; const updatedUser = await updateUser(updateData); if (updatedUser) { setCurrentUser(updatedUser); showMessage('Perfil actualizado exitosamente', 'success'); } } catch (error) { showMessage(error.message || 'Error al actualizar el perfil', 'error'); } finally { setSaving(false); } }; // Manejar cambio de foto const handlePhotoChange = async (event) => { const file = event.target.files[0]; if (!file) return; // Validar tipo de archivo if (!file.type.startsWith('image/')) { showMessage('Por favor selecciona un archivo de imagen válido', 'error'); return; } // Validar tamaño (5MB máximo) if (file.size > 5 * 1024 * 1024) { showMessage('La imagen debe ser menor a 5MB', 'error'); return; } setSaving(true); try { const token = localStorage.getItem('access'); const API_URL = import.meta.env.VITE_EFC_API_URL; const formDataPhoto = new FormData(); formDataPhoto.append('profile_picture', file); const response = await fetch(`${API_URL}/user/users/${currentUser.id}/`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${token}`, }, body: formDataPhoto, }); if (response.status === 401) { showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); localStorage.removeItem('access'); localStorage.removeItem('refresh'); setTimeout(() => { window.location.href = '/login'; }, 2000); return; } if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Error al actualizar la foto'); } const updatedUser = await response.json(); setCurrentUser(updatedUser); showMessage('Foto de perfil actualizada exitosamente', 'success'); } catch (error) { console.error('Error updating photo:', error); showMessage(error.message || 'Error al actualizar la foto', 'error'); } finally { setSaving(false); } }; // Función para abrir selector de archivos const handlePhotoClick = () => { fileInputRef.current?.click(); }; // Verificar si hay cambios en el formulario const hasChanges = () => { if (!currentUser) return false; return ( formData.first_name !== (currentUser.first_name || '') || formData.last_name !== (currentUser.last_name || '') || formData.email !== (currentUser.email || '') // RFC excluded from change detection - field is read-only ); }; // Solo mostrar tabs permitidas si es importador const isImportador = typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true'; const tabs = [ { id: 'profile', name: 'Perfil', icon: 'user' }, { id: 'organization', name: 'Organización', icon: 'building' }, { id: 'security', name: 'Seguridad', icon: 'shield' }, { id: 'notifications', name: 'Notificaciones', icon: 'bell' } ].filter(tab => isImportador ? tab.id === 'profile' || tab.id === 'security' || tab.id === 'notifications' : true ); const getTabIcon = (iconType) => { const icons = { user: ( ), building: ( ), shield: ( ), bell: ( ) }; return icons[iconType]; }; const renderProfileTab = () => (

Información Personal

Perfil
{loading ? (
) : ( <> {/* Avatar y información básica mejorada */}
{currentUser?.profile_picture ? ( Avatar del usuario ) : (
)}

{currentUser ? `${currentUser.first_name} ${currentUser.last_name}` : 'Usuario'}

@{currentUser?.username || 'usuario'}

ID: {currentUser?.id || 'Sin ID'}

{/* Input oculto para seleccionar archivo */}
{/* Formulario de información mejorado */}

El username no puede ser modificado por razones de seguridad

{currentUser?.rfc !== undefined && (

El RFC no puede ser modificado por razones de seguridad

)}
{hasChanges() && (
Tienes cambios sin guardar
)}
)}
); const renderOrganizationTab = () => (

Configuración de Organización

Organización

Gestiona la configuración relacionada con tu organización y equipos de trabajo.

Funcionalidad en desarrollo

Las configuraciones de organización estarán disponibles próximamente. Esta sección incluirá:

  • Gestión de equipos y departamentos
  • Configuración de permisos y roles
  • Preferencias de la organización
  • Integración con sistemas externos
  • Configuración de flujos de trabajo
{/* Información actual de la organización */} {currentUser?.organizacion && (

Información actual

ID de organización

{currentUser.organizacion}

Tu rol

{typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true' ? 'Importador' : 'Administrador'}

)}
); const renderSecurityTab = () => (

Seguridad de la cuenta

Seguridad
{/* Cambiar contraseña */}

Cambiar contraseña

Mantén tu cuenta segura con una contraseña fuerte y única.

{passwordErrors.current_password && (

{passwordErrors.current_password}

)}
{passwordErrors.new_password && (

{passwordErrors.new_password}

)}
{passwordErrors.confirm_password && (

{passwordErrors.confirm_password}

)}
{/* Información de seguridad adicional */}

Requisitos de contraseña

Mínimo 8 caracteres de longitud

Al menos una letra minúscula (a-z)

Al menos una letra mayúscula (A-Z)

Al menos un número (0-9)

Consejos adicionales

Evita usar información personal como nombres o fechas de nacimiento

No reutilices contraseñas de otras cuentas

Considera usar símbolos especiales para mayor seguridad

); const renderNotificationsTab = () => (

Preferencias de notificaciones

Notificaciones
{/* Notificaciones por email */}

Recibir notificaciones importantes por correo electrónico

{/* Notificaciones de documentos */}

Notificar cuando se suban o actualicen documentos

{/* Notificaciones del sistema */}

Recibir actualizaciones del sistema y mantenimiento

{/* Información adicional */}

Información importante

Las notificaciones críticas de seguridad y las alertas del sistema siempre se enviarán, independientemente de tus preferencias de notificación.

); const renderTabContent = () => { switch (activeTab) { case 'profile': return renderProfileTab(); case 'organization': return renderOrganizationTab(); case 'security': return renderSecurityTab(); case 'notifications': return renderNotificationsTab(); default: return renderProfileTab(); } }; return (
{/* Header mejorado con gradiente azul */}

Configuración

Gestiona tu perfil, configuración de cuenta y preferencias del sistema

{currentUser ? `${currentUser.first_name} ${currentUser.last_name}` : 'Usuario'}
{/* Navegación responsive para móviles */}

Configuración

{tabs.map((tab) => ( ))}
{/* Sidebar de navegación mejorado - solo visible en desktop */} {/* Contenido principal mejorado */}
{renderTabContent()}
); }; export default Settings;