Se agrego funcionalidad a procesos

This commit is contained in:
2025-08-06 16:40:39 -06:00
parent fc4552bf49
commit a49db17607
3 changed files with 1871 additions and 638 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@ const initialForm = {
first_name: '', first_name: '',
last_name: '', last_name: '',
password: '', password: '',
confirmPassword: '',
rfc: '', // Agregar campo RFC para importadores
}; };
export default function Users() { export default function Users() {
@@ -44,6 +46,20 @@ export default function Users() {
const [itemsPerPage, setItemsPerPage] = useState(10); const [itemsPerPage, setItemsPerPage] = useState(10);
const { showMessage } = useNotification(); const { showMessage } = useNotification();
// Estados para validación de contraseña
const [passwordValidation, setPasswordValidation] = useState({
length: false,
uppercase: false,
lowercase: false,
number: false,
special: false,
});
const [showPasswordValidation, setShowPasswordValidation] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [passwordsMatch, setPasswordsMatch] = useState(true);
const [showPasswordMatchValidation, setShowPasswordMatchValidation] = useState(false);
const loadUsers = () => { const loadUsers = () => {
setLoading(true); setLoading(true);
fetchUsers() fetchUsers()
@@ -107,7 +123,52 @@ export default function Users() {
}, [searchTerm]); }, [searchTerm]);
const handleChange = e => { const handleChange = e => {
setForm({ ...form, [e.target.name]: e.target.value }); const { name, value } = e.target;
setForm({ ...form, [name]: value });
// Validar contraseña en tiempo real
if (name === 'password') {
validatePassword(value);
// Validar coincidencia si ya hay confirmación
if (form.confirmPassword) {
validatePasswordMatch(value, form.confirmPassword);
}
}
// Validar coincidencia de contraseñas
if (name === 'confirmPassword') {
validatePasswordMatch(form.password, value);
}
};
// Función para validar contraseña
const validatePassword = (password) => {
const validation = {
length: password.length >= 8,
uppercase: /[A-Z]/.test(password),
lowercase: /[a-z]/.test(password),
number: /\d/.test(password),
special: /[!@#$%^&*(),.?":{}|<>]/.test(password),
};
setPasswordValidation(validation);
setShowPasswordValidation(password.length > 0);
};
// Función para validar coincidencia de contraseñas
const validatePasswordMatch = (password, confirmPassword) => {
const match = password === confirmPassword;
setPasswordsMatch(match);
setShowPasswordMatchValidation(confirmPassword.length > 0);
};
// Verificar si la contraseña es válida
const isPasswordValid = () => {
return Object.values(passwordValidation).every(valid => valid);
};
// Verificar si el formulario es válido para envío
const isFormValid = () => {
return isPasswordValid() && passwordsMatch && form.password.length > 0 && form.confirmPassword.length > 0;
}; };
// (Eliminado duplicado de handleSubmit, ahora solo existe la versión unificada más abajo) // (Eliminado duplicado de handleSubmit, ahora solo existe la versión unificada más abajo)
@@ -160,6 +221,19 @@ export default function Users() {
setShowEditModal(false); setShowEditModal(false);
setShowDeleteModal(false); setShowDeleteModal(false);
setUserToDelete(null); setUserToDelete(null);
// Resetear estados de validación de contraseña
setPasswordValidation({
length: false,
uppercase: false,
lowercase: false,
number: false,
special: false,
});
setShowPasswordValidation(false);
setShowPassword(false);
setShowConfirmPassword(false);
setPasswordsMatch(true);
setShowPasswordMatchValidation(false);
}; };
// Función para normalizar texto (quitar acentos y espacios extra) // Función para normalizar texto (quitar acentos y espacios extra)
@@ -981,138 +1055,329 @@ export default function Users() {
{/* Modales */} {/* Modales */}
{/* Modal Crear Usuario */} {/* Modal Crear Usuario */}
{showCreateModal && ( {showCreateModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div className="fixed inset-0 bg-black bg-opacity-60 backdrop-blur-sm overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4">
<div className="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white"> <div className="relative mx-auto w-full max-w-2xl bg-white rounded-2xl shadow-2xl transform transition-all duration-300 animate-in slide-in-from-bottom-4">
<div className="mt-3"> {/* Header formal con gradiente */}
<div className="flex items-center justify-between mb-4"> <div className={`${createType === 'importador' ? 'bg-gradient-to-r from-green-700 to-green-900' : 'bg-gradient-to-r from-blue-700 to-blue-900'} rounded-t-2xl p-4 text-white border-b-2 ${createType === 'importador' ? 'border-green-500' : 'border-blue-500'}`}>
<h3 className="text-lg font-medium text-gray-900">Crear Nuevo Usuario</h3> <div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className={`${createType === 'importador' ? 'bg-green-500' : 'bg-blue-500'} bg-opacity-30 rounded-xl p-2 border ${createType === 'importador' ? 'border-green-400' : 'border-blue-400'} border-opacity-30`}>
{createType === 'importador' ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
)}
</div>
<div>
<h3 className="text-lg font-semibold tracking-wide">
{createType === 'importador' ? 'Registro de Nuevo Importador' : 'Registro de Nuevo Agente'}
</h3>
<p className={`${createType === 'importador' ? 'text-green-200' : 'text-blue-200'} text-xs font-medium`}>
{createType === 'importador' ? 'Sistema de Gestión de Importadores' : 'Sistema de Gestión de Agentes Aduanales'}
</p>
</div>
</div>
<button <button
onClick={handleCancel} onClick={handleCancel}
className="text-gray-400 hover:text-gray-600 transition-colors" className={`${createType === 'importador' ? 'text-green-100 hover:text-white hover:bg-green-600' : 'text-blue-100 hover:text-white hover:bg-blue-600'} transition-colors p-2 hover:bg-opacity-50 rounded-lg border ${createType === 'importador' ? 'border-green-500' : 'border-blue-500'} border-opacity-30`}
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
</div> </div>
</div>
{/* Contenido del formulario */}
<form onSubmit={handleSubmit} className="space-y-4"> <div className="p-6">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> <form onSubmit={handleSubmit} className="space-y-5">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"> {/* Sección de Información Personal */}
Nombre de usuario * <div className="bg-slate-50 rounded-xl p-4 border border-slate-200">
</label> <div className="flex items-center mb-3 pb-2 border-b border-slate-300">
<input <div className={`${createType === 'importador' ? 'bg-green-600' : 'bg-blue-600'} rounded-lg p-2 mr-3 shadow-sm`}>
type="text" <svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
name="username" <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
value={form.username} </svg>
onChange={handleChange} </div>
required <div>
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors" <h4 className="text-sm font-semibold text-slate-800">Información Personal</h4>
placeholder="nombre_usuario" <p className="text-xs text-slate-600">Datos de identificación del usuario</p>
/> </div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div> <div className="space-y-1">
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-xs font-semibold text-slate-700">
Email * Nombre de usuario <span className="text-red-600">*</span>
</label> </label>
<input <input
type="email" type="text"
name="email" name="username"
value={form.email} value={form.username}
onChange={handleChange} onChange={handleChange}
required required
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors" className="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm"
placeholder="usuario@ejemplo.com" placeholder="nombre_usuario"
/> />
</div> </div>
<div className="space-y-1">
{/* Campo RFC solo para importador */} <label className="block text-xs font-semibold text-slate-700">
{createType === 'importador' && ( Correo electrónico <span className="text-red-600">*</span>
<> </label>
<div className="sm:col-span-2"> <input
<label className="block text-sm font-medium text-gray-700 mb-1"> type="email"
RFC del Importador * name="email"
</label> value={form.email}
<input onChange={handleChange}
type="text" required
name="rfc" className="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm"
value={form.rfc || ''} placeholder="usuario@ejemplo.com"
onChange={handleChange} />
required </div>
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm transition-colors" <div className="space-y-1">
placeholder="RFC del importador" <label className="block text-xs font-semibold text-slate-700">
/> Nombre
</div> </label>
{/* Campo oculto para is_importador */} <input
<input type="hidden" name="is_importador" value="true" /> type="text"
</> name="first_name"
)} value={form.first_name}
onChange={handleChange}
<div> className="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm"
<label className="block text-sm font-medium text-gray-700 mb-1"> placeholder="Nombre del usuario"
Nombre />
</label> </div>
<input <div className="space-y-1">
type="text" <label className="block text-xs font-semibold text-slate-700">
name="first_name" Apellido
value={form.first_name} </label>
onChange={handleChange} <input
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors" type="text"
placeholder="Nombre" name="last_name"
/> value={form.last_name}
</div> onChange={handleChange}
className="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm"
<div> placeholder="Apellido del usuario"
<label className="block text-sm font-medium text-gray-700 mb-1"> />
Apellido </div>
</label>
<input
type="text"
name="last_name"
value={form.last_name}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors"
placeholder="Apellido"
/>
</div>
<div className="sm:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
Contraseña *
</label>
<input
type="password"
name="password"
value={form.password}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors"
placeholder="Contraseña del usuario"
/>
</div> </div>
</div> </div>
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200"> {/* Sección de RFC (solo para importador) */}
{createType === 'importador' && (
<div className="bg-green-50 rounded-xl p-4 border border-green-200">
<div className="flex items-center mb-3 pb-2 border-b border-green-300">
<div className="bg-green-700 rounded-lg p-2 mr-3 shadow-sm">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<h4 className="text-sm font-semibold text-slate-800">Información Fiscal</h4>
<p className="text-xs text-slate-600">Datos fiscales del importador</p>
</div>
</div>
<div className="space-y-1">
<label className="block text-xs font-semibold text-slate-700">
RFC del Importador <span className="text-red-600">*</span>
</label>
<input
type="text"
name="rfc"
value={form.rfc || ''}
onChange={handleChange}
required
maxLength="13"
className="w-full px-3 py-2 border border-green-300 rounded-md shadow-sm focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm font-mono uppercase"
placeholder="RFC13CARACTERES"
style={{ textTransform: 'uppercase' }}
/>
<p className="text-xs text-green-600 mt-1">Formato: 12-13 caracteres (ABCD123456ABC)</p>
</div>
<input type="hidden" name="is_importador" value="true" />
</div>
)}
{/* Sección de Seguridad */}
<div className="bg-slate-50 rounded-xl p-4 border border-slate-200">
<div className="flex items-center mb-3 pb-2 border-b border-slate-300">
<div className="bg-red-600 rounded-lg p-2 mr-3 shadow-sm">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<div>
<h4 className="text-sm font-semibold text-slate-800">Credenciales de Acceso</h4>
<p className="text-xs text-slate-600">Configuración de seguridad de la cuenta</p>
</div>
</div>
<div className="space-y-1">
<label className="block text-xs font-semibold text-slate-700">
Contraseña <span className="text-red-600">*</span>
</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
name="password"
value={form.password}
onChange={handleChange}
required
className={`w-full px-3 py-2 pr-10 border rounded-md shadow-sm focus:ring-2 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm ${
showPasswordValidation && isPasswordValid()
? 'border-green-300 focus:ring-green-500 focus:border-green-500'
: showPasswordValidation
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
: 'border-slate-300 focus:ring-red-500 focus:border-red-500'
}`}
placeholder="Contraseña segura del usuario"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-slate-400 hover:text-slate-600"
>
{showPassword ? (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L8.464 8.464m1.414 1.414L21.536 21.536" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
)}
</button>
</div>
{/* Indicadores de validación de contraseña */}
{showPasswordValidation && (
<div className="mt-3 p-3 bg-slate-100 rounded-lg border">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold text-slate-700">Requisitos de contraseña:</span>
{isPasswordValid() && (
<div className="flex items-center text-green-600">
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-xs font-medium">Válida</span>
</div>
)}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div className={`flex items-center text-xs ${passwordValidation.length ? 'text-green-600' : 'text-red-500'}`}>
<svg className="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={passwordValidation.length ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M6 18L18 6M6 6l12 12"} />
</svg>
Mínimo 8 caracteres
</div>
<div className={`flex items-center text-xs ${passwordValidation.uppercase ? 'text-green-600' : 'text-red-500'}`}>
<svg className="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={passwordValidation.uppercase ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M6 18L18 6M6 6l12 12"} />
</svg>
Una letra mayúscula
</div>
<div className={`flex items-center text-xs ${passwordValidation.lowercase ? 'text-green-600' : 'text-red-500'}`}>
<svg className="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={passwordValidation.lowercase ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M6 18L18 6M6 6l12 12"} />
</svg>
Una letra minúscula
</div>
<div className={`flex items-center text-xs ${passwordValidation.number ? 'text-green-600' : 'text-red-500'}`}>
<svg className="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={passwordValidation.number ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M6 18L18 6M6 6l12 12"} />
</svg>
Un número
</div>
<div className={`flex items-center text-xs sm:col-span-2 ${passwordValidation.special ? 'text-green-600' : 'text-red-500'}`}>
<svg className="w-3 h-3 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={passwordValidation.special ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M6 18L18 6M6 6l12 12"} />
</svg>
Un carácter especial (!@#$%^&*(),.?":{}|&lt;&gt;)
</div>
</div>
</div>
)}
{/* Campo de confirmación de contraseña */}
<div className="space-y-1 mt-4">
<label className="block text-xs font-semibold text-slate-700">
Confirmar Contraseña <span className="text-red-600">*</span>
</label>
<div className="relative">
<input
type={showConfirmPassword ? "text" : "password"}
name="confirmPassword"
value={form.confirmPassword}
onChange={handleChange}
required
className={`w-full px-3 py-2 pr-10 border rounded-md shadow-sm focus:ring-2 transition-all duration-200 bg-white text-slate-900 placeholder-slate-400 text-sm ${
showPasswordMatchValidation && passwordsMatch && form.confirmPassword.length > 0
? 'border-green-300 focus:ring-green-500 focus:border-green-500'
: showPasswordMatchValidation && !passwordsMatch
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
: 'border-slate-300 focus:ring-red-500 focus:border-red-500'
}`}
placeholder="Confirme la contraseña"
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-slate-400 hover:text-slate-600"
>
{showConfirmPassword ? (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L8.464 8.464m1.414 1.414L21.536 21.536" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
)}
</button>
</div>
{/* Indicador de coincidencia de contraseñas */}
{showPasswordMatchValidation && (
<div className={`mt-2 flex items-center text-xs ${passwordsMatch ? 'text-green-600' : 'text-red-500'}`}>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={passwordsMatch ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M6 18L18 6M6 6l12 12"} />
</svg>
<span className="font-medium">
{passwordsMatch ? 'Las contraseñas coinciden' : 'Las contraseñas no coinciden'}
</span>
</div>
)}
</div>
</div>
</div>
{/* Botones de acción */}
<div className="flex flex-col sm:flex-row justify-end space-y-2 sm:space-y-0 sm:space-x-3 pt-4 border-t border-slate-200">
<button <button
type="button" type="button"
onClick={handleCancel} onClick={handleCancel}
disabled={submitting} disabled={submitting}
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-50" className="w-full sm:w-auto px-6 py-2 border border-slate-300 rounded-md shadow-sm text-sm font-semibold text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 transition-all duration-200 disabled:opacity-50"
> >
Cancelar Cancelar
</button> </button>
<button <button
type="submit" type="submit"
disabled={submitting} disabled={submitting || (showPasswordValidation && !isFormValid())}
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-50 flex items-center" className={`w-full sm:w-auto px-6 py-2 border border-transparent rounded-md shadow-lg text-sm font-semibold text-white ${createType === 'importador' ? 'bg-gradient-to-r from-green-700 to-green-900 hover:from-green-800 hover:to-green-950 focus:ring-green-500' : 'bg-gradient-to-r from-blue-700 to-blue-900 hover:from-blue-800 hover:to-blue-950 focus:ring-blue-500'} focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 flex items-center justify-center space-x-2 disabled:opacity-50 disabled:cursor-not-allowed`}
> >
{submitting && ( {submitting && (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
)} )}
{submitting ? 'Creando...' : 'Crear Usuario'} <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>{submitting ? 'Creando...' : (createType === 'importador' ? 'Crear Importador' : 'Crear Agente')}</span>
</button> </button>
</div> </div>
</form> </form>

File diff suppressed because it is too large Load Diff