Aprobación de Agencias

This commit is contained in:
2025-07-14 13:27:15 -06:00
parent e820f81d31
commit 0e88fea37e
36 changed files with 2807 additions and 1128 deletions

View File

@@ -469,7 +469,8 @@ function aprobar_agencia()
$usuario_id = $_SESSION['usuario_id']; $usuario_id = $_SESSION['usuario_id'];
if (!$id || !is_numeric($id)) { if (!$id || !is_numeric($id)) {
die("❌ ID inválido."); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=invalid_id");
exit;
} }
// Obtener la solicitud // Obtener la solicitud
@@ -478,7 +479,8 @@ function aprobar_agencia()
$solicitud = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); $solicitud = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
if (!$solicitud) { if (!$solicitud) {
die("❌ Solicitud no encontrada."); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=invalid_request");
exit;
} }
// Validar que no haya sido aprobada ya // Validar que no haya sido aprobada ya
@@ -511,6 +513,7 @@ function aprobar_agencia()
// Insertar en agencias_aduanales // Insertar en agencias_aduanales
$sqlInsert = "INSERT INTO agencias_aduanales $sqlInsert = "INSERT INTO agencias_aduanales
(nombre_agencia, rfc_agencia, direccion, telefono, email, creado_en, creado_por) (nombre_agencia, rfc_agencia, direccion, telefono, email, creado_en, creado_por)
OUTPUT INSERTED.id_agencia
VALUES (?, ?, ?, ?, ?, GETDATE(), ?) VALUES (?, ?, ?, ?, ?, GETDATE(), ?)
"; ";
$stmtInsert = sqlsrv_query($conn, $sqlInsert, [ $stmtInsert = sqlsrv_query($conn, $sqlInsert, [
@@ -518,37 +521,38 @@ function aprobar_agencia()
]); ]);
if (!$stmtInsert) { if (!$stmtInsert) {
die("❌ Error al crear agencia: " . print_r(sqlsrv_errors(), true)); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=save_agencia_failed");
exit;
} }
// Obtener ID de la agencia recién creada // Obtener ID de la agencia recién creada
$sqlGetAgenciaId = "SELECT SCOPE_IDENTITY() AS id_agencia"; $agenciaIdRow = sqlsrv_fetch_array($stmtInsert, SQLSRV_FETCH_ASSOC);
$stmtGetAgenciaId = sqlsrv_query($conn, $sqlGetAgenciaId);
$agenciaIdRow = sqlsrv_fetch_array($stmtGetAgenciaId, SQLSRV_FETCH_ASSOC);
$id_agencia = $agenciaIdRow['id_agencia'] ?? null; $id_agencia = $agenciaIdRow['id_agencia'] ?? null;
if (!$id_agencia) { if (!$id_agencia) {
die("❌ No se pudo obtener el ID de la agencia recién creada."); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=get_id_failed");
exit;
} }
// Insertar en usuarios_sistema // Insertar en usuarios_sistema
$sqlInsert2 = "INSERT INTO usuarios_sistema (nombre, email, password_hash, tipo_usuario, activo, creado_en, dos_factores, creado_por) $sqlInsert2 = "INSERT INTO usuarios_sistema (nombre, email, password_hash, tipo_usuario, activo, creado_en, dos_factores, creado_por)
OUTPUT INSERTED.id_usuario
VALUES (?, ?, ?, ?, 1, GETDATE(), 0, ?) VALUES (?, ?, ?, ?, 1, GETDATE(), 0, ?)
"; ";
$stmtInsert2 = sqlsrv_query($conn, $sqlInsert2, [$admin_name_encrypt, $admin_email_encrypt, $password_hash, $tipo, $usuario_id]); $stmtInsert2 = sqlsrv_query($conn, $sqlInsert2, [$admin_name_encrypt, $admin_email_encrypt, $password_hash, $tipo, $usuario_id]);
if (!$stmtInsert2) { if (!$stmtInsert2) {
die("❌ Error al crear usuario administrador: " . print_r(sqlsrv_errors(), true)); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=error_user");
exit;
} }
// Obtener ID del nuevo usuario // Obtener ID del nuevo usuario
$sqlGetId = "SELECT SCOPE_IDENTITY() AS id_usuario"; $idUsuarioRow = sqlsrv_fetch_array($stmtInsert2, SQLSRV_FETCH_ASSOC);
$stmtGetId = sqlsrv_query($conn, $sqlGetId);
$idUsuarioRow = sqlsrv_fetch_array($stmtGetId, SQLSRV_FETCH_ASSOC);
$id_usuario = $idUsuarioRow['id_usuario'] ?? null; $id_usuario = $idUsuarioRow['id_usuario'] ?? null;
if (!$id_usuario) { if (!$id_usuario) {
die("❌ No se pudo obtener el ID del usuario recién creado."); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=error_id");
exit;
} }
// Actualizar agencia con el ID del administrador // Actualizar agencia con el ID del administrador
@@ -556,7 +560,8 @@ function aprobar_agencia()
$stmtUpdateAgencia = sqlsrv_query($conn, $sqlUpdateAgencia, [$id_usuario, $id_agencia]); $stmtUpdateAgencia = sqlsrv_query($conn, $sqlUpdateAgencia, [$id_usuario, $id_agencia]);
if (!$stmtUpdateAgencia) { if (!$stmtUpdateAgencia) {
die("❌ Error al actualizar agencia con administrador: " . print_r(sqlsrv_errors(), true)); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=update_failed");
exit;
} }
// Insertar en informacion_general // Insertar en informacion_general
@@ -564,7 +569,8 @@ function aprobar_agencia()
$stmtInfo = sqlsrv_query($conn, $sqlInfo, [$id_usuario, $admin_name, $admin_email]); $stmtInfo = sqlsrv_query($conn, $sqlInfo, [$id_usuario, $admin_name, $admin_email]);
if (!$stmtInfo) { if (!$stmtInfo) {
die("❌ Error al insertar información general: " . print_r(sqlsrv_errors(), true)); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=update_failed");
exit;
} }
// Actualizar solicitud // Actualizar solicitud
@@ -577,7 +583,8 @@ function aprobar_agencia()
$stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$usuario_id, $id]); $stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$usuario_id, $id]);
if (!$stmtUpdate) { if (!$stmtUpdate) {
die("❌ Error al actualizar solicitud: " . print_r(sqlsrv_errors(), true)); header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=update_request_failed");
exit;
} }
// Enviar correo a la agencia // Enviar correo a la agencia
@@ -624,7 +631,7 @@ function aprobar_agencia()
// Registrar en bitácora // Registrar en bitácora
registrar_bitacora_agencia($id_agencia, $nombre, 'CREACION', $usuario_id); registrar_bitacora_agencia($id_agencia, $nombre, 'CREACION', $usuario_id);
header("Location: /IMPORTADORES/administrador/agenciasActivas"); header("Location: /IMPORTADORES/administrador/agenciasActivas?success=created");
exit; exit;
} }

View File

@@ -72,8 +72,6 @@ function alta()
FROM agente_agencia aa FROM agente_agencia aa
INNER JOIN usuarios_sistema u INNER JOIN usuarios_sistema u
ON aa.id_agente = u.id_usuario ON aa.id_agente = u.id_usuario
INNER JOIN agencias_aduanales a
ON aa.id_agente = a.id_administrador
LEFT JOIN usuarios_sistema creador LEFT JOIN usuarios_sistema creador
ON u.creado_por = creador.id_usuario ON u.creado_por = creador.id_usuario
WHERE aa.id_agencia = ? WHERE aa.id_agencia = ?
@@ -108,6 +106,12 @@ function guardarAgente()
$conn = getConnection(); $conn = getConnection();
try { try {
// === AGREGADO: LOG DE DEPURACIÓN ===
error_log("=== INICIO DEBUG guardarAgente ===");
error_log("POST data: " . print_r($_POST, true));
error_log("SESSION usuario_id: " . $_SESSION['usuario_id']);
error_log("SESSION tipo_usuario: " . $_SESSION['tipo_usuario']);
if (!sqlsrv_begin_transaction($conn)) { if (!sqlsrv_begin_transaction($conn)) {
error_log("ERROR: No se pudo iniciar la transacción"); error_log("ERROR: No se pudo iniciar la transacción");
throw new Exception("Error al iniciar transacción"); throw new Exception("Error al iniciar transacción");
@@ -121,13 +125,25 @@ function guardarAgente()
$password = $_POST['password'] ?? ''; $password = $_POST['password'] ?? '';
$tipo = $_POST['tipo_usuario'] ?? ''; $tipo = $_POST['tipo_usuario'] ?? '';
// === AGREGADO: VALIDACIÓN DETALLADA ===
error_log("Datos capturados:");
error_log("nombre: '$nombre'");
error_log("email: '$email'");
error_log("password: " . (empty($password) ? 'VACÍO' : 'NO VACÍO'));
error_log("tipo: '$tipo'");
if (empty($nombre) || empty($email) || empty($password) || empty($tipo)) { if (empty($nombre) || empty($email) || empty($password) || empty($tipo)) {
error_log("ERROR: Datos incompletos");
error_log("nombre vacío: " . (empty($nombre) ? 'SÍ' : 'NO'));
error_log("email vacío: " . (empty($email) ? 'SÍ' : 'NO'));
error_log("password vacío: " . (empty($password) ? 'SÍ' : 'NO'));
error_log("tipo vacío: " . (empty($tipo) ? 'SÍ' : 'NO'));
header("Location: /IMPORTADORES/agencias/alta?error=invalid_data"); header("Location: /IMPORTADORES/agencias/alta?error=invalid_data");
exit; exit;
} }
if ($tipo !== 'agente_aduanal') { if ($tipo !== 'agente_aduanal') {
error_log("DEBUG 5: Tipo de usuario inválido: $tipo"); error_log("ERROR: Tipo de usuario inválido: '$tipo'");
header("Location: /IMPORTADORES/agencias/alta?error=invalid_user_type"); header("Location: /IMPORTADORES/agencias/alta?error=invalid_user_type");
exit; exit;
} }
@@ -137,15 +153,27 @@ function guardarAgente()
$email_encrypted = encrypt($email); $email_encrypted = encrypt($email);
$password_hash = password_hash($password, PASSWORD_DEFAULT); $password_hash = password_hash($password, PASSWORD_DEFAULT);
error_log("Datos encriptados exitosamente");
// 3. Validar duplicado por email encriptado // 3. Validar duplicado por email encriptado
$sqlCheck = "SELECT COUNT(*) AS total FROM usuarios_sistema WHERE email = ?"; $sqlCheck = "SELECT COUNT(*) AS total FROM usuarios_sistema WHERE email = ?";
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$email_encrypted]); $stmtCheck = sqlsrv_query($conn, $sqlCheck, [$email_encrypted]);
if ($stmtCheck === false) {
error_log("ERROR: Fallo en consulta de duplicado");
error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al verificar duplicado");
}
$rowCheck = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC); $rowCheck = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
if ($rowCheck['total'] > 0) { if ($rowCheck['total'] > 0) {
error_log("ERROR: Email ya existe");
header("Location: /IMPORTADORES/agencias/alta?error=email_exists"); header("Location: /IMPORTADORES/agencias/alta?error=email_exists");
exit; exit;
} }
error_log("Validación de duplicado pasada");
// 4. Insertar usuario // 4. Insertar usuario
$sqlInsert = "INSERT INTO usuarios_sistema $sqlInsert = "INSERT INTO usuarios_sistema
(nombre, email, password_hash, tipo_usuario, activo, creado_en, dos_factores, creado_por) (nombre, email, password_hash, tipo_usuario, activo, creado_en, dos_factores, creado_por)
@@ -153,10 +181,20 @@ function guardarAgente()
VALUES (?, ?, ?, ?, 1, GETDATE(), 0, ?) VALUES (?, ?, ?, ?, 1, GETDATE(), 0, ?)
"; ";
$params = [$nombre_encrypted, $email_encrypted, $password_hash, $tipo, $_SESSION['usuario_id']]; $params = [$nombre_encrypted, $email_encrypted, $password_hash, $tipo, $_SESSION['usuario_id']];
error_log("Parámetros para INSERT:");
error_log("nombre_encrypted: " . (empty($nombre_encrypted) ? 'VACÍO' : 'OK'));
error_log("email_encrypted: " . (empty($email_encrypted) ? 'VACÍO' : 'OK'));
error_log("password_hash: " . (empty($password_hash) ? 'VACÍO' : 'OK'));
error_log("tipo: '$tipo'");
error_log("creado_por: " . $_SESSION['usuario_id']);
$stmtInsert = sqlsrv_query($conn, $sqlInsert, $params); $stmtInsert = sqlsrv_query($conn, $sqlInsert, $params);
if ($stmtInsert === false) { if ($stmtInsert === false) {
error_log("ERROR: Fallo al insertar usuario"); error_log("ERROR: Fallo al insertar usuario");
error_log("SQL: $sqlInsert");
error_log("Errores SQL Server:");
error_log(print_r(sqlsrv_errors(), true)); error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al insertar usuario"); throw new Exception("Error al insertar usuario");
} }
@@ -166,41 +204,67 @@ function guardarAgente()
if (!$id_usuario) { if (!$id_usuario) {
error_log("ERROR: No se obtuvo id_usuario tras el insert"); error_log("ERROR: No se obtuvo id_usuario tras el insert");
error_log("idUsuarioRow: " . print_r($idUsuarioRow, true));
throw new Exception("Error al obtener ID del nuevo usuario"); throw new Exception("Error al obtener ID del nuevo usuario");
} }
// 6. Obtener la agencia del administrador actual error_log("Usuario insertado exitosamente con ID: $id_usuario");
// 5. Obtener la agencia del administrador actual
$sqlAgencia = "SELECT id_agencia FROM agencias_aduanales WHERE id_administrador = ?"; $sqlAgencia = "SELECT id_agencia FROM agencias_aduanales WHERE id_administrador = ?";
$stmtAgencia = sqlsrv_query($conn, $sqlAgencia, [$_SESSION['usuario_id']]); $stmtAgencia = sqlsrv_query($conn, $sqlAgencia, [$_SESSION['usuario_id']]);
if ($stmtAgencia === false) {
error_log("ERROR: Fallo al consultar agencia");
error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al consultar agencia");
}
$agenciaRow = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC); $agenciaRow = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC);
$id_agencia = $agenciaRow['id_agencia'] ?? null; $id_agencia = $agenciaRow['id_agencia'] ?? null;
if (!$id_agencia) { if (!$id_agencia) {
error_log("ERROR: No se encontró agencia para el admin_agencia con id_usuario=" . $_SESSION['usuario_id']); error_log("ERROR: No se encontró agencia para el admin_agencia con id_usuario=" . $_SESSION['usuario_id']);
error_log("agenciaRow: " . print_r($agenciaRow, true));
throw new Exception("No se encontró agencia asociada al administrador"); throw new Exception("No se encontró agencia asociada al administrador");
} }
// 7. Crear la relación agente-agencia error_log("Agencia encontrada con ID: $id_agencia");
// 6. Crear la relación agente-agencia
$sqlRelacion = "INSERT INTO agente_agencia $sqlRelacion = "INSERT INTO agente_agencia
(id_agente, id_agencia, fecha_asignacion, activo, asignado_por) (id_agente, id_agencia, fecha_asignacion, activo, asignado_por)
VALUES (?, ?, GETDATE(), 1, ?) VALUES (?, ?, GETDATE(), 1, ?)
"; ";
$paramsRelacion = [$id_usuario, $id_agencia, $_SESSION['usuario_id']]; $paramsRelacion = [$id_usuario, $id_agencia, $_SESSION['usuario_id']];
error_log("Parámetros para relación agente-agencia:");
error_log("id_agente: $id_usuario");
error_log("id_agencia: $id_agencia");
error_log("asignado_por: " . $_SESSION['usuario_id']);
$stmtRelacion = sqlsrv_query($conn, $sqlRelacion, $paramsRelacion); $stmtRelacion = sqlsrv_query($conn, $sqlRelacion, $paramsRelacion);
if ($stmtRelacion === false) { if ($stmtRelacion === false) {
error_log("ERROR: No se pudo insertar la relación agente-agencia"); error_log("ERROR: No se pudo insertar la relación agente-agencia");
error_log("SQL: $sqlRelacion");
error_log("Errores SQL Server:");
error_log(print_r(sqlsrv_errors(), true)); error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al guardar relación agente-agencia"); throw new Exception("Error al guardar relación agente-agencia");
} }
// 8. CONFIRMAR TRANSACCIÓN ✅ error_log("Relación agente-agencia creada exitosamente");
// 7. CONFIRMAR TRANSACCIÓN ✅
if (!sqlsrv_commit($conn)) { if (!sqlsrv_commit($conn)) {
error_log("ERROR: No se pudo confirmar la transacción"); error_log("ERROR: No se pudo confirmar la transacción");
error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al confirmar transacción"); throw new Exception("Error al confirmar transacción");
} }
// 9. Enviar correo de bienvenida error_log("Transacción confirmada exitosamente");
// 8. Enviar correo de bienvenida
if (class_exists('PHPMailer\PHPMailer\PHPMailer')) { if (class_exists('PHPMailer\PHPMailer\PHPMailer')) {
$mail = new PHPMailer(true); $mail = new PHPMailer(true);
try { try {
@@ -253,25 +317,30 @@ function guardarAgente()
</div>"; </div>";
$mail->send(); $mail->send();
error_log("Correo enviado exitosamente");
} catch (Exception $e) { } catch (Exception $e) {
error_log("Error al enviar correo: " . $e->getMessage()); error_log("Error al enviar correo: " . $e->getMessage());
} }
} }
// 10. Redirigir con mensaje de éxito error_log("=== FIN EXITOSO guardarAgente ===");
// 9. Redirigir con mensaje de éxito
header("Location: /IMPORTADORES/agencias/alta?success=created"); header("Location: /IMPORTADORES/agencias/alta?success=created");
exit; exit;
} catch (Exception $e) { } catch (Exception $e) {
error_log("ERROR CAPTURADO: " . $e->getMessage()); error_log("ERROR CAPTURADO: " . $e->getMessage());
error_log("Stack trace: " . $e->getTraceAsString());
if (!sqlsrv_rollback($conn)) { if (!sqlsrv_rollback($conn)) {
error_log("ERROR: No se pudo revertir la transacción"); error_log("ERROR: No se pudo revertir la transacción");
error_log(print_r(sqlsrv_errors(), true));
} else { } else {
error_log("Transacción revertida exitosamente"); error_log("Transacción revertida exitosamente");
} }
error_log("=== FIN CON ERROR guardar_agencia ==="); error_log("=== FIN CON ERROR guardarAgente ===");
header("Location: /IMPORTADORES/agencias/alta?error=save_failed"); header("Location: /IMPORTADORES/agencias/alta?error=save_failed");
exit; exit;
} finally { } finally {

View File

@@ -241,7 +241,7 @@ function guardar()
sqlsrv_free_stmt($stmt); sqlsrv_free_stmt($stmt);
sqlsrv_close($conn); sqlsrv_close($conn);
header("Location: /IMPORTADORES/patente/lista?success=1"); header("Location: /IMPORTADORES/patente/lista?created=ok");
exit; exit;
} }
@@ -317,14 +317,33 @@ function actualizar()
$curp = trim($_POST['curp'] ?? ''); $curp = trim($_POST['curp'] ?? '');
$razon_social = trim($_POST['razon_social'] ?? ''); $razon_social = trim($_POST['razon_social'] ?? '');
$id_usuario = $_SESSION['usuario_id']; $id_usuario = $_SESSION['usuario_id'];
$tipo_usuario = $_SESSION['tipo_usuario'];
// ✅ NUEVO: Obtener la agencia actual del usuario // Obtener id_agencia del usuario actual
$stmt = sqlsrv_query($conn, "SELECT id_agencia FROM dbo.agente_agencia WHERE id_agente = ?", [$id_usuario]);
$id_agencia = null; $id_agencia = null;
if ($tipo_usuario === 'agente_aduanal' || $tipo_usuario === 'admin_agencia') {
$stmt = sqlsrv_query($conn, "SELECT id_agencia FROM dbo.agente_agencia WHERE id_agente = ?", [$id_usuario]);
if ($stmt && $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { if ($stmt && $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$id_agencia = $row['id_agencia']; $id_agencia = $row['id_agencia'];
} }
}
if (!$id_agencia) {
die("❌ No se pudo determinar la agencia del usuario.");
}
// Verificar que el registro existe y pertenece a la agencia del usuario
$sqlVerificar = "SELECT COUNT(*) as count FROM dbo.agentes_aduanales WHERE id_agente = ? AND id_agencia = ?";
$stmtVerificar = sqlsrv_query($conn, $sqlVerificar, [$id_agente, $id_agencia]);
if ($stmtVerificar === false) {
die("❌ Error al verificar el registro: " . print_r(sqlsrv_errors(), true));
}
$row = sqlsrv_fetch_array($stmtVerificar, SQLSRV_FETCH_ASSOC);
if ($row['count'] == 0) {
die("❌ El registro no existe o no tienes permisos para editarlo.");
}
$mf_nombre = trim($_POST['mf_nombre'] ?? ''); $mf_nombre = trim($_POST['mf_nombre'] ?? '');
$mf_paterno = trim($_POST['mf_paterno'] ?? ''); $mf_paterno = trim($_POST['mf_paterno'] ?? '');
@@ -351,6 +370,7 @@ function actualizar()
$vae_final = trim($_POST['vae_final'] ?? ''); $vae_final = trim($_POST['vae_final'] ?? '');
$vae_siguiente = trim($_POST['vae_siguiente'] ?? ''); $vae_siguiente = trim($_POST['vae_siguiente'] ?? '');
// Validaciones
if (!$id_agente || empty($aduana) || empty($patente) || empty($agente_aduanal) || empty($rfc) || empty($curp) || empty($razon_social)) { if (!$id_agente || empty($aduana) || empty($patente) || empty($agente_aduanal) || empty($rfc) || empty($curp) || empty($razon_social)) {
die("❌ Datos inválidos o incompletos."); die("❌ Datos inválidos o incompletos.");
} }
@@ -360,19 +380,16 @@ function actualizar()
if (strlen($rfc) > 13) die("❌ El RFC no puede exceder 13 caracteres."); if (strlen($rfc) > 13) die("❌ El RFC no puede exceder 13 caracteres.");
if (strlen($curp) > 18) die("❌ El CURP no puede exceder 18 caracteres."); if (strlen($curp) > 18) die("❌ El CURP no puede exceder 18 caracteres.");
// Validar duplicado (excepto el propio id) // Validar duplicado
$sqlCheck = "SELECT COUNT(*) as count FROM dbo.agentes_aduanales WHERE aduana = ? AND patente = ? AND id_usuario = ? AND id_agente != ?"; $sqlCheck = "SELECT COUNT(*) as count FROM dbo.agentes_aduanales WHERE aduana = ? AND patente = ? AND id_agencia = ? AND id_agente != ?";
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$aduana, $patente, $id_usuario, $id_agente]); $stmtCheck = sqlsrv_query($conn, $sqlCheck, [$aduana, $patente, $id_agencia, $id_agente]);
if ($stmtCheck === false) die("❌ Error al verificar duplicados."); if ($stmtCheck === false) die("❌ Error al verificar duplicados.");
$row = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC); $row = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
if ($row['count'] > 0) die("❌ Ya existe un agente aduanal con esta combinación de aduana y patente."); if ($row['count'] > 0) die("❌ Ya existe un agente aduanal con esta combinación de aduana y patente.");
sqlsrv_free_stmt($stmtCheck); // Convertir valores vacíos a NULL
// Convertir valores vacíos a NULL para la base de datos
$vp_inicio = empty($vp_inicio) ? null : $vp_inicio; $vp_inicio = empty($vp_inicio) ? null : $vp_inicio;
$vp_final = empty($vp_final) ? null : $vp_final; $vp_final = empty($vp_final) ? null : $vp_final;
$vp_siguiente = empty($vp_siguiente) ? null : $vp_siguiente; $vp_siguiente = empty($vp_siguiente) ? null : $vp_siguiente;
@@ -399,7 +416,7 @@ function actualizar()
vcc_inicio = ?, vcc_final = ?, vcc_siguiente = ?, vcc_inicio = ?, vcc_final = ?, vcc_siguiente = ?,
vae_inicio = ?, vae_final = ?, vae_siguiente = ?, id_agencia = ? vae_inicio = ?, vae_final = ?, vae_siguiente = ?, id_agencia = ?
WHERE id_agente = ? WHERE id_agente = ?
AND id_usuario = ? AND id_agencia = ?
"; ";
$params = [ $params = [
$aduana, $patente, $agente_aduanal, $rfc, $curp, $razon_social, $aduana, $patente, $agente_aduanal, $rfc, $curp, $razon_social,
@@ -409,11 +426,19 @@ function actualizar()
$vat_pb_inicio, $vat_pb_final, $vat_pb_siguiente, $vat_pb_inicio, $vat_pb_final, $vat_pb_siguiente,
$vcc_inicio, $vcc_final, $vcc_siguiente, $vcc_inicio, $vcc_final, $vcc_siguiente,
$vae_inicio, $vae_final, $vae_siguiente, $id_agencia, $vae_inicio, $vae_final, $vae_siguiente, $id_agencia,
$id_agente, $id_usuario $id_agente, $id_agencia
]; ];
$stmt = sqlsrv_query($conn, $sql, $params); $stmt = sqlsrv_query($conn, $sql, $params);
if ($stmt === false) die("❌ Error al actualizar agente aduanal."); if ($stmt === false) {
die("❌ Error al actualizar: " . print_r(sqlsrv_errors(), true));
}
$rows_affected = sqlsrv_rows_affected($stmt);
if ($rows_affected === 0) {
die("❌ No se actualizó ningún registro. Verifica los datos.");
}
sqlsrv_free_stmt($stmt); sqlsrv_free_stmt($stmt);
sqlsrv_close($conn); sqlsrv_close($conn);

View File

@@ -1,4 +1,5 @@
<?php <?php
// app/controllers/proveedores.php
require_once __DIR__ . '/../helpers/session.php'; require_once __DIR__ . '/../helpers/session.php';
require_once __DIR__ . '/../../config/database.php'; require_once __DIR__ . '/../../config/database.php';
// 1) Composer autoload (phpdotenv y demás libs) // 1) Composer autoload (phpdotenv y demás libs)
@@ -32,7 +33,7 @@ function getApiToken(): ?string
$pass = $_ENV['API_PASS'] ?? ''; $pass = $_ENV['API_PASS'] ?? '';
$body = json_encode(['username' => $user, 'password' => $pass]); $body = json_encode(['username' => $user, 'password' => $pass]);
error_log("[getApiToken] POST $url$body"); error_log("[getApiToken] POST $url");
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
@@ -46,7 +47,7 @@ function getApiToken(): ?string
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch); curl_close($ch);
error_log("[getApiToken] HTTP $http$resp"); error_log("[getApiToken] HTTP $http");
if ($http === 200 && ($data = json_decode($resp, true)) && !empty($data['token'])) { if ($http === 200 && ($data = json_decode($resp, true)) && !empty($data['token'])) {
// 3) Guardamos token y tiempo actual // 3) Guardamos token y tiempo actual
@@ -87,47 +88,71 @@ function ajax_lista()
$token = getApiToken(); $token = getApiToken();
if (!$token) { if (!$token) {
error_log('[ajax_lista] Sin token válido'); error_log('[ajax_lista] Sin token válido');
// Devolvemos estructura vacía echo json_encode(['data' => [], 'error' => 'No se pudo obtener token de autenticación']);
echo json_encode(['data' => []]);
return; return;
} }
// 3) Construimos la URL de la API // CORRECCIÓN: Usar la misma estructura de URL que en otras funciones
$apiBase = rtrim($_ENV['API_URL'] ?? '', '/'); $apiBase = rtrim($_ENV['API_URL'] ?? '', '/');
$url = $apiBase . '/proveedores'; $url = $apiBase . '/api/proveedores'; // Cambié para que sea consistente
error_log("[ajax_lista] GET $url"); error_log("[ajax_lista] GET $url");
// 4) Ejecutamos cURL // 4) Ejecutamos cURL
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: $token", 'Accept: application/json'], // CORRECCIÓN: Agregar "Bearer" al token
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'],
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5, CURLOPT_TIMEOUT => 10, // Aumenté el timeout
]); ]);
$resp = curl_exec($ch); $resp = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch); curl_close($ch);
error_log("[ajax_lista] HTTP $status$resp"); error_log("[ajax_lista] HTTP $status");
// MEJORA: Verificar errores de cURL
if ($curlError) {
error_log("[ajax_lista] cURL Error: $curlError");
echo json_encode(['data' => [], 'error' => 'Error de conexión con la API']);
return;
}
// MEJORA: Mejor manejo de errores
if ($status !== 200) {
error_log("[ajax_lista] Error HTTP $status: $resp");
echo json_encode(['data' => [], 'error' => "Error HTTP $status"]);
return;
}
$json = json_decode($resp, true);
if (!is_array($json)) {
error_log("[ajax_lista] Respuesta no es un array válido: $resp");
echo json_encode(['data' => [], 'error' => 'Respuesta inválida de la API']);
return;
}
error_log("[ajax_lista] Procesando " . count($json) . " proveedores");
// 5) Parseamos y formateamos
$dataList = []; $dataList = [];
if ($status === 200 && ($json = json_decode($resp, true)) && is_array($json)) {
foreach ($json as $p) { foreach ($json as $p) {
$clave = htmlspecialchars($p['Clave'] ?? '', ENT_QUOTES); $clave = htmlspecialchars($p['Clave'] ?? '', ENT_QUOTES);
// Construir dirección // Construir dirección de forma más robusta
$direccion = trim(implode(', ', array_filter([ $direccionParts = array_filter([
$p['Calles'] ?? '', $p['Calles'] ?? '',
'Num. Ext: ' . ($p['NumExt'] ?? ''), ($p['NumExt'] ?? '') ? 'Num. Ext: ' . $p['NumExt'] : '',
'Num. Int: ' . ($p['NumInt'] ?? ''), ($p['NumInt'] ?? '') ? 'Num. Int: ' . $p['NumInt'] : '',
$p['Colonia'] ?? '', $p['Colonia'] ?? '',
$p['Municipio'] ?? '', $p['Municipio'] ?? '',
$p['Ciudad'] ?? '', $p['Ciudad'] ?? '',
'C.P. ' . ($p['CodigoPostal'] ?? ''), ($p['CodigoPostal'] ?? '') ? 'C.P. ' . $p['CodigoPostal'] : '',
$p['EntidadFederativa'] ?? '', $p['EntidadFederativa'] ?? '',
$p['Pais'] ?? '' $p['Pais'] ?? ''
]))); ]);
$direccion = implode(', ', $direccionParts);
$dataList[] = [ $dataList[] = [
$clave, $clave,
@@ -135,15 +160,11 @@ function ajax_lista()
htmlspecialchars($p['RFC'] ?? '', ENT_QUOTES), htmlspecialchars($p['RFC'] ?? '', ENT_QUOTES),
htmlspecialchars($p['Ciudad'] ?? '', ENT_QUOTES), htmlspecialchars($p['Ciudad'] ?? '', ENT_QUOTES),
htmlspecialchars($p['Telefono'] ?? '', ENT_QUOTES), htmlspecialchars($p['Telefono'] ?? '', ENT_QUOTES),
htmlspecialchars($direccion ?? '', ENT_QUOTES), htmlspecialchars($direccion, ENT_QUOTES),
// Acciones
"<a href=\"/IMPORTADORES/proveedores/editar?clave=" . rawurlencode($clave) . "\" class=\"btn btn-sm btn-primary\">✏️</a> "<a href=\"/IMPORTADORES/proveedores/editar?clave=" . rawurlencode($clave) . "\" class=\"btn btn-sm btn-primary\">✏️</a>
<button class=\"btn btn-sm btn-danger\" onclick=\"confirmDelete('{$clave}')\">🗑️</button>" <button class=\"btn btn-sm btn-danger\" onclick=\"confirmDelete('{$clave}')\">🗑️</button>"
]; ];
} }
} else {
error_log('[ajax_lista] Respuesta inválida o status != 200');
}
// 6) Devolvemos siempre HTTP 200 con data (posiblemente vacío) // 6) Devolvemos siempre HTTP 200 con data (posiblemente vacío)
echo json_encode(['data' => $dataList]); echo json_encode(['data' => $dataList]);
@@ -161,20 +182,24 @@ function eliminar()
exit; exit;
} }
// CORRECCIÓN: Usar la misma URL base y corregir el Authorization header
$url = rtrim($_ENV['API_URL'] ?? '', '/') . '/api/proveedores/' . urlencode($clave); $url = rtrim($_ENV['API_URL'] ?? '', '/') . '/api/proveedores/' . urlencode($clave);
error_log("[eliminar] DELETE $url"); error_log("[eliminar] DELETE $url");
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ["Authorization: $token",'Accept: application/json'], // CORRECCIÓN: Quitar el espacio extra y agregar Bearer
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'],
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5, CURLOPT_TIMEOUT => 5,
]); ]);
curl_exec($ch); $resp = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch); curl_close($ch);
error_log("[eliminar] HTTP $status: $resp");
header('Location: /IMPORTADORES/proveedores?deleted=' . ($status===200||$status===204 ? 'ok':'error')); header('Location: /IMPORTADORES/proveedores?deleted=' . ($status===200||$status===204 ? 'ok':'error'));
exit; exit;
} }

View File

@@ -73,7 +73,7 @@ function guardar()
$vehiculo = trim($_POST['vehiculo'] ?? ''); $vehiculo = trim($_POST['vehiculo'] ?? '');
$identFiscal = trim($_POST['identificador_fiscal'] ?? ''); $identFiscal = trim($_POST['identificador_fiscal'] ?? '');
$idTrans = $_POST['id_transportista'] ?? null; $idTrans = $_POST['transportista_id'] ?? null;
if ($vehiculo === '' || $identFiscal === '' || !$idTrans) { if ($vehiculo === '' || $identFiscal === '' || !$idTrans) {
die("❌ Todos los campos son obligatorios."); die("❌ Todos los campos son obligatorios.");

View File

@@ -40,7 +40,7 @@ function guardar()
} }
// Redirigir de vuelta a la lista o mostrar mensaje… // Redirigir de vuelta a la lista o mostrar mensaje…
header("Location: /IMPORTADORES/transportistas/lista?success=1"); header("Location: /IMPORTADORES/transportistas/lista?created=ok");
exit; exit;
} }
@@ -113,7 +113,7 @@ function lista()
include __DIR__ . '/../../views/transportistas/lista.php'; include __DIR__ . '/../../views/transportistas/lista.php';
} }
/** Descarga la plantilla CSV para carga masiva **/ /** Descarga la plantilla CSV para carga masiva **/
function template() function template()
{ {
$file = __DIR__ . '/../../public/downloads/transportistas_template.csv'; $file = __DIR__ . '/../../public/downloads/transportistas_template.csv';

View File

@@ -129,6 +129,16 @@
</script> </script>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Agencia registrada',
text: 'La agencia aduanal se ha registrada correctamente y se ha enviado un correo con las credenciales de acceso al administrador.',
confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?> <?php if (isset($_GET['error'])): ?>
<script> <script>
<?php if ($_GET['error'] === 'unauthorized'): ?> <?php if ($_GET['error'] === 'unauthorized'): ?>

View File

@@ -62,58 +62,50 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
table.dataTable thead th { background:#343a40; color:#fff; } table.dataTable thead th { background:#343a40; color:#fff; }
/* ========== ANIMACIONES PARA INPUTS ========== */ /* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
/* 1. Animación al hacer focus */ .form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; } .form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); } .form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 2. Animación al escribir */ .form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
.form-control:not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 3. Efecto de shake en validación */
.form-control.shake { animation: shake 0.5s ease-in-out; }
@keyframes shake { @keyframes shake {
0%, 100% { transform: translateX(0); } 0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); } 25% { transform: translateX(-5px); }
75% { transform: translateX(5px); } 75% { transform: translateX(5px); }
} }
/* 4. Floating labels con animación */ /* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; } .form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; } .form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; } .form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label, .form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } .form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
/* 5. Efecto de pulso en hover */ .input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse { @keyframes inputPulse {
0% { transform: scale(1); } 0% { transform: scale(1); }
50% { transform: scale(1.02); } 50% { transform: scale(1.02); }
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
/* 6. Gradiente animado en el borde */ .input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient { position: relative; overflow: hidden; } .input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
.input-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; } background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:focus::before { left: 100%; } .input-gradient:not(.no-animation):focus::before { left: 100%; }
/* 7. Efecto de brillo en validación exitosa */ .form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
.form-control.valid { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow { @keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } 0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); } 50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } 100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
} }
/* 8. Typing effect simulation */ .input-typing:not(.no-animation) { position: relative; }
.input-typing { position: relative; } .input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; } .input-typing:not(.no-animation):focus::after { opacity: 1; }
.input-typing:focus::after { opacity: 1; }
@keyframes blink { @keyframes blink {
0%, 50% { opacity: 1; } 0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; } 51%, 100% { opacity: 0; }
} }
/* 9. Efecto de ondas */ .input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple { position: relative; overflow: hidden; } .input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
.input-ripple::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(13, 110, 253, 0.2); background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; } .input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
.input-ripple:focus::before { width: 300px; height: 300px; } /* Animación para los campos del formulario */
/* 10. Slide-up animation para los campos */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; } .form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; } .form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; } .form-group-animated:nth-child(2) { animation-delay: 0.2s; }
@@ -123,49 +115,45 @@
.form-group-animated:nth-child(6) { animation-delay: 0.6s; } .form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; } .form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; } .form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } } @keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */ /* ========== ANIMACIONES PARA SELECT ========== */
/* 1. Estilos base para select */ .form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
.form-select {transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; } background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
/* 2. Animación al hacer focus */
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); .form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); } background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
/* 3. Animación cuando tiene valor seleccionado */ .form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
.form-select:not([value=""]):not(:invalid) { border-color: #198754; background-color: #f8fff9; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; } .form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
.form-floating-custom .form-select:focus ~ .form-label, /* Floating labels para select */
.form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } .form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
/* 5. Efecto de shake en validación para select */ transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; } .form-select.shake { animation: shake 0.5s ease-in-out; }
/* 6. Efecto de pulso en hover para select */
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; } .select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
/* 7. Gradiente animado para select */
.select-gradient { position: relative; overflow: hidden; } .select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; } .select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; } .select-gradient:focus::before { left: 100%; }
/* 8. Efecto de brillo en validación exitosa para select */
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; } .form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
/* 9. Efecto de rotación de la flecha al abrir */
.select-arrow-rotate { transition: all 0.3s ease; } .select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); } .select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
/* 10. Efecto de ondas para select */
.select-ripple { position: relative; overflow: hidden; } .select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; .select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; } background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; } .select-ripple:focus::after { width: 300px; height: 300px; }
/* 11. Efecto de scale suave */
.select-scale { transition: transform 0.2s ease; } .select-scale { transition: transform 0.2s ease; }
.select-scale:hover { transform: scale(1.02); } .select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); } .select-scale:focus { transform: scale(1.02) translateY(-2px); }
/* 12. Personalización de opciones (limitado en algunos navegadores) */
.form-select option { padding: 0.5rem; transition: all 0.2s ease; } .form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; } .form-select option:hover { background-color: #f8f9fa; }
/* 13. Indicador de estado */
.select-status { position: relative; } .select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%); .select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; } .select-status.valid::after { background-color: #198754; opacity: 1; } width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; } .select-status:invalid::after { background-color: #dc3545; opacity: 1; }
</style> </style>
</head> </head>
@@ -200,15 +188,15 @@
<div class="col-md-2 form-group-animated"> <div class="col-md-2 form-group-animated">
<div class="form-floating-custom"> <div class="form-floating-custom">
<select name="tipo_usuario" id="tipo" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required> <select name="tipo_usuario" id="tipo" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value=""></option> <option value="">-- Tipo de Usuario --</option>
<option value="super_admin">Administrador General</option> <option value="super_admin">Administrador General</option>
<option value="importador">Importador</option> <option value="importador">Importador</option>
</select> </select>
<label for="tipo" class="form-label">Tipo</label> <label for="tipo" class="form-label">Tipo de Usuario</label>
</div> </div>
</div> </div>
</div> </div>
<div class="text-end mt-4"> <div class="text-end mt-4 form-group-animated">
<button class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button> <button class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button>
</div> </div>
</form> </form>
@@ -286,6 +274,28 @@
}); });
}); });
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// Validación del formulario // Validación del formulario
document.getElementById('formAltaUsuarios').addEventListener('submit', function(e) { document.getElementById('formAltaUsuarios').addEventListener('submit', function(e) {
e.preventDefault(); e.preventDefault();
@@ -297,17 +307,6 @@
isValid = false; isValid = false;
} }
}); });
if (isValid) {
// Aquí puedes agregar tu lógica de envío
Swal.fire({
icon: 'success',
title: '¡Éxito!',
text: 'Agencia registrada correctamente',
showConfirmButton: false,
timer: 1500
});
}
}); });
}); });
</script> </script>

View File

@@ -128,5 +128,29 @@
}); });
</script> </script>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'error_user'): ?>
Swal.fire({ icon: 'error', title: 'Error de creación', text: 'El usuario administrador no se creo correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'get_id_agencia'): ?>
Swal.fire({ icon: 'error', title: 'Error de obtención', text: 'El identificador no se pudo obtener correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_request'): ?>
Swal.fire({ icon: 'error', title: 'ID de solicitud inválido', text: 'El identificador de la solicitud no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_agencia_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar agencia', text: 'No se pudo guardar la agencia. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el administrador de la agencia.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_request_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'Error al actualizar la solicitud.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'get_id_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error de obtención', text: 'Error al obtener el id de la agencia recien creada.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body> </body>
</html> </html>

View File

@@ -61,6 +61,99 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
table.dataTable thead th { background: #343a40; color: #fff; } table.dataTable thead th { background: #343a40; color: #fff; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
.input-typing:not(.no-animation) { position: relative; }
.input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:not(.no-animation):focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */
.form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
/* Floating labels para select */
.form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; }
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; }
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
.select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
.select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; }
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
.select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; }
</style> </style>
</head> </head>
<body> <body>
@@ -70,26 +163,38 @@
<h3 class="mb-4 animate__animated animate__fadeInDown title_glow">👨‍✈️ Administración de Agentes</h3> <h3 class="mb-4 animate__animated animate__fadeInDown title_glow">👨‍✈️ Administración de Agentes</h3>
<!-- FORMULARIO --> <!-- FORMULARIO -->
<div class="card p-4 mb-4 shadow-sm bg-white card-hover position-relative h-auto"> <div class="card p-4 mb-4 shadow-sm bg-white card-hover position-relative h-auto">
<form action="/IMPORTADORES/agencias/guardarAgente" method="POST"> <form action="/IMPORTADORES/agencias/guardarAgente" method="POST" id="formAltaAgentes">
<h5 class="mb-3"> Nuevo Agente</h5> <h5 class="mb-3"> Nuevo Agente</h5>
<div class="row g-3"> <div class="row g-3">
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<input name="nombre" class="form-control" placeholder="Nombre completo" required> <div class="form-floating-custom">
<input name="nombre" id="nombre" type="text" class="form-control input-pulse input-gradient" placeholder=" " required>
<label for="nombre" class="form-label">Nombre *</label>
</div> </div>
<div class="col-md-4">
<input name="email" type="email" class="form-control" placeholder="Correo electrónico" required>
</div> </div>
<div class="col-md-2"> <div class="col-md-4 form-group-animated">
<input name="password" type="password" class="form-control" placeholder="Contraseña" required> <div class="form-floating-custom">
<input name="email" type="email" id="email" class="form-control input-pulse input-typing" placeholder=" " required>
<label for="email" class="form-label">Correo *</label>
</div> </div>
<div class="col-md-2"> </div>
<select name="tipo_usuario" class="form-select" required> <div class="col-md-2 form-group-animated">
<option value="">Tipo</option> <div class="form-floating-custom">
<input name="password" type="password" id="password" class="form-control input-pulse input-ripple" placeholder=" " required>
<label for="password" class="form-label">Contraseña *</label>
</div>
</div>
<div class="col-md-2 form-group-animated">
<div class="form-floating-custom">
<select name="tipo_usuario" id="tipo" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Tipo de Usuario --</option>
<option value="agente_aduanal">Agente Aduanal</option> <option value="agente_aduanal">Agente Aduanal</option>
</select> </select>
<label for="tipo" class="form-label">Tipo de Usuario</label>
</div> </div>
</div> </div>
<div class="text-end mt-4"> </div>
<div class="text-end mt-4 form-group-animated">
<button class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button> <button class="btn btn-success px-4 mt-auto w-auto btn-animated">Registrar</button>
</div> </div>
</form> </form>
@@ -147,14 +252,112 @@
} }
}); });
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form-select');
const selects = document.querySelectorAll('select.form-select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// UN SOLO EVENT LISTENER PARA EL FORMULARIO
document.getElementById('formAltaAgentes').addEventListener('submit', function(e) {
console.log('Formulario enviándose...');
console.log('Datos del formulario:');
const formData = new FormData(this);
for (let [key, value] of formData.entries()) {
console.log(key + ': ' + value);
}
// Verificar que todos los campos requeridos estén llenos
const nombre = document.getElementById('nombre').value;
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const tipo = document.getElementById('tipo').value;
if (!nombre || !email || !password || !tipo) {
console.error('Faltan campos requeridos');
console.log('nombre:', nombre);
console.log('email:', email);
console.log('password:', password ? 'NO VACÍO' : 'VACÍO');
console.log('tipo:', tipo);
// Mostrar animación de error en campos vacíos
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
}
});
e.preventDefault(); // Solo prevenir si hay errores
return false;
}
if (tipo !== 'agente_aduanal') {
console.error('Tipo de usuario inválido:', tipo);
e.preventDefault(); // Solo prevenir si hay errores
return false;
}
console.log('Validación del formulario pasada');
// Deshabilitar botón de envío para evitar envíos múltiples
const submitButton = this.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.disabled = true;
submitButton.innerHTML = 'Enviando...';
}
// NO HACER e.preventDefault() aquí - dejar que el formulario se envíe normalmente
// El formulario se enviará automáticamente a la acción especificada
});
});
</script> </script>
<?php if (isset($_GET['success'])): ?> <?php if (isset($_GET['success'])): ?>
<script> <script>
<?php if ($_GET['success'] === 'created'): ?> <?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Usuario creado', Swal.fire({ icon: 'success', title: 'Usuario creado', text: 'El usuario se ha creado correctamente y se ha enviado un correo con las credenciales.', confirmButtonColor: '#198754' });
text: 'El usuario se ha creado correctamente y se ha enviado un correo con las credenciales.',
confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'status_deactivated'): ?> <?php elseif ($_GET['success'] === 'status_deactivated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' }); Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?> <?php endif; ?>

View File

@@ -169,8 +169,8 @@
.form-floating-fecha .form-control:not(:placeholder-shown) ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; } .form-floating-fecha .form-control:not(:placeholder-shown) ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-fecha .form-control:focus, .form-floating-fecha .form-control:not(:placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; } .form-floating-fecha .form-control:focus, .form-floating-fecha .form-control:not(:placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */ /* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: 1px solid #ccc !important; box-shadow: none !important; background: #fff !important; } .no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: 1px solid #0d6efd !important; box-shadow: none !important; } .no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; } .no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; } .no-animation::before, .no-animation::after { display: none !important; }
</style> </style>
@@ -290,6 +290,7 @@
const telefonoRegex = /^\d{3}\s\d{7}$/; const telefonoRegex = /^\d{3}\s\d{7}$/;
const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const soloNumerosRegex = /^[0-9]+$/; const soloNumerosRegex = /^[0-9]+$/;
const fechaRegex = /^\d{2}\/\d{2}\/\d{4}$/; // formato dd/mm/aaaa
// VALIDAR CAMPOS OBLIGATORIOS PRIMERO // VALIDAR CAMPOS OBLIGATORIOS PRIMERO
if (!transportista_id) { if (!transportista_id) {
@@ -320,7 +321,7 @@
if (!soloNumerosRegex.test(numero_licencia)) { if (!soloNumerosRegex.test(numero_licencia)) {
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545' Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545'
}); });
document.getElementById('Clave').focus(); document.getElementById('numero_licencia').focus();
return; return;
} }
// VALIDAR CAMPOS OPCIONALES (solo si tienen contenido) // VALIDAR CAMPOS OPCIONALES (solo si tienen contenido)
@@ -336,8 +337,19 @@
} }
// VALIDAR FECHA DE INGRESO (solo si se proporciona) // VALIDAR FECHA DE INGRESO (solo si se proporciona)
if (fecha_ingreso) { if (fecha_ingreso) {
const fechaIngreso = new Date(fecha_ingreso); // Validar formato primero
if (!fechaRegex.test(fecha_ingreso)) {
Swal.fire({ icon: 'error', title: 'Formato de fecha inválido', text: 'La fecha debe tener el formato dd/mm/aaaa', confirmButtonColor: '#dc3545' });
document.getElementById('fecha_ingreso').focus();
return;
}
// Convertir fecha correctamente (dd/mm/aaaa -> mm/dd/aaaa)
const [dia, mes, año] = fecha_ingreso.split('/');
const fechaIngreso = new Date(año, mes - 1, dia); // mes - 1 porque Date usa 0-11
const fechaActual = new Date(); const fechaActual = new Date();
fechaActual.setHours(0, 0, 0, 0); // Normalizar a medianoche
if (fechaIngreso > fechaActual) { if (fechaIngreso > fechaActual) {
Swal.fire({ icon: 'error', title: 'Fecha inválida', text: 'La fecha de ingreso no puede ser futura.', confirmButtonColor: '#dc3545' }); Swal.fire({ icon: 'error', title: 'Fecha inválida', text: 'La fecha de ingreso no puede ser futura.', confirmButtonColor: '#dc3545' });
document.getElementById('fecha_ingreso').focus(); document.getElementById('fecha_ingreso').focus();
@@ -415,21 +427,6 @@
isValid = false; isValid = false;
} }
}); });
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
} else {
// Mostrar mensaje de error
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
}
}); });
}); });
@@ -465,56 +462,6 @@
} }
}); });
// Validación específica para campos de texto
document.addEventListener('DOMContentLoaded', function() {
// Validación del número de licencia (solo números y letras)
const licenciaInput = document.getElementById('numero_licencia');
if (licenciaInput) {
licenciaInput.addEventListener('input', function() {
this.value = this.value.replace(/[^A-Za-z0-9]/g, '');
});
}
// Validación del teléfono (solo números)
const telefonoInput = document.getElementById('telefono');
if (telefonoInput) {
telefonoInput.addEventListener('input', function() {
this.value = this.value.replace(/[^0-9]/g, '');
});
}
// Validación del email en tiempo real
const emailInput = document.getElementById('email');
if (emailInput) {
emailInput.addEventListener('input', function() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (this.value && !emailRegex.test(this.value)) {
this.setCustomValidity('Por favor, ingrese un email válido');
} else {
this.setCustomValidity('');
}
});
}
});
// Función para mostrar notificaciones
function showNotification(message, type = 'info') {
// Crear el elemento de notificación
const notification = document.createElement('div');
notification.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`;
notification.style.cssText = ` top: 20px; right: 20px; z-index: 9999; min-width: 300px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);`;
notification.innerHTML = `${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
document.body.appendChild(notification);
// Auto-remover después de 5 segundos
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
// Inicializar Flatpickr para la fecha // Inicializar Flatpickr para la fecha
flatpickr("#fecha_ingreso", { flatpickr("#fecha_ingreso", {
dateFormat: "d/m/Y", dateFormat: "d/m/Y",

View File

@@ -14,6 +14,12 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales --> <!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<!-- CSS de Flatpickr -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<!-- JS de Flatpickr -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<!-- Idioma español -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/es.js"></script>
<style> <style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; } body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; } .sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
@@ -58,6 +64,114 @@
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; .btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
.input-typing:not(.no-animation) { position: relative; }
.input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:not(.no-animation):focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */
.form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
/* Floating labels para select */
.form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; }
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; }
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
.select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
.select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; }
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
.select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; }
/* ========== ESTILOS PARA CAMPO FECHA ========== */
.form-floating-fecha { position: relative; margin-bottom: 1.5rem; }
.form-floating-fecha .form-control { padding: 1rem 0.75rem; transition: all 0.3s ease; border: 2px solid #dee2e6; }
.form-floating-fecha .form-control:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-floating-fecha .form-control:not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-floating-fecha .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%);pointer-events: none;
transition: all 0.3s ease; color: #6c757d; opacity: 0; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; }
.form-floating-fecha .form-control:focus ~ .form-label,
.form-floating-fecha .form-control:not(:placeholder-shown) ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-fecha .form-control:focus, .form-floating-fecha .form-control:not(:placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style> </style>
</head> </head>
<body> <body>
@@ -68,10 +182,12 @@
<form action="/IMPORTADORES/choferes/actualizar" method="POST" enctype="multipart/form-data" id="formEditChoferes"> <form action="/IMPORTADORES/choferes/actualizar" method="POST" enctype="multipart/form-data" id="formEditChoferes">
<input type="hidden" name="id_chofer" value="<?= (int)$chofer['id_chofer'] ?>"> <input type="hidden" name="id_chofer" value="<?= (int)$chofer['id_chofer'] ?>">
<div class="row mb-3">
<!-- Transportista --> <!-- Transportista -->
<div class="mb-3"> <div class="col-md-12 form-group-animated">
<label for="transportista_id" class="form-label">Transportista</label> <label for="transportista_id" class="form-label">Transportista</label>
<select name="transportista_id" id="transportista_id" class="form-select" required> <div class="form-floating-custom">
<select name="transportista_id" id="transportista_id" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un transportista --</option> <option value="">-- Selecciona un transportista --</option>
<?php foreach ($transportistas as $t): ?> <?php foreach ($transportistas as $t): ?>
<option value="<?= $t['id_transportista'] ?>" <option value="<?= $t['id_transportista'] ?>"
@@ -80,65 +196,90 @@
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="transportista_id" class="form-label">Transportista</label>
</div>
</div> </div>
<div class="row">
<!-- Nombre --> <!-- Nombre -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="nombre" class="form-label">Nombre</label> <label for="nombre" class="form-label">Nombre</label>
<input name="nombre" id="nombre" type="text" class="form-control" <div class="form-floating-custom">
<input name="nombre" id="nombre" type="text" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['nombre']) ?>" required> value="<?= htmlspecialchars($chofer['nombre']) ?>" required>
<label for="nombre" class="form-label">Nombre</label>
</div>
</div> </div>
<!-- Apellido --> <!-- Apellido -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="apellido" class="form-label">Apellido</label> <label for="apellido" class="form-label">Apellido</label>
<input name="apellido" id="apellido" type="text" class="form-control" <div class="form-floating-custom">
<input name="apellido" id="apellido" type="text" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['apellido']) ?>" required> value="<?= htmlspecialchars($chofer['apellido']) ?>" required>
<label for="apellido" class="form-label">Apellido</label>
</div> </div>
</div> </div>
<div class="row">
<!-- Licencia --> <!-- Licencia -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="numero_licencia" class="form-label">Número de Licencia</label> <label for="numero_licencia" class="form-label">Número de Licencia</label>
<input name="numero_licencia" id="numero_licencia" type="text" maxlength="11" class="form-control" <div class="form-floating-custom">
<input name="numero_licencia" id="numero_licencia" type="text" maxlength="11" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['numero_licencia']) ?>" required> value="<?= htmlspecialchars($chofer['numero_licencia']) ?>" required>
<label for="numero_licencia" class="form-label">Número de Licencia</label>
</div>
</div> </div>
<!-- Gafete --> <!-- Gafete -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="numero_gafete" class="form-label">Número de Gafete</label> <label for="numero_gafete" class="form-label">Número de Gafete</label>
<input name="numero_gafete" id="numero_gafete" type="text" maxlength="24" class="form-control" <div class="form-floating-custom">
<input name="numero_gafete" id="numero_gafete" type="text" maxlength="24" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['numero_gafete']) ?>" required> value="<?= htmlspecialchars($chofer['numero_gafete']) ?>" required>
<label for="numero_gafete" class="form-label">Número de Gafete</label>
</div>
</div> </div>
<!-- Teléfono --> <!-- Teléfono -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="telefono" class="form-label">Teléfono</label> <label for="telefono" class="form-label">Teléfono</label>
<input name="telefono" id="telefono" type="tel" maxlength="11" class="form-control" <div class="form-floating-custom">
<input name="telefono" id="telefono" type="tel" maxlength="11" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['telefono']) ?>"> value="<?= htmlspecialchars($chofer['telefono']) ?>">
<label for="telefono" class="form-label">Teléfono</label>
</div> </div>
</div> </div>
<div class="row">
<!-- Email --> <!-- Email -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="email" class="form-label">Email</label> <label for="email" class="form-label">Email</label>
<input name="email" id="email" type="email" class="form-control" <div class="form-floating-custom">
<input name="email" id="email" type="email" class="form-control input-pulse input-typing"
value="<?= htmlspecialchars($chofer['email']) ?>"> value="<?= htmlspecialchars($chofer['email']) ?>">
<label for="email" class="form-label">Email</label>
</div>
</div> </div>
<!-- Fecha de Ingreso --> <!-- Fecha de Ingreso -->
<div class="col-md-6 mb-3"> <div class="col-md-6 form-group-animated">
<label for="fecha_ingreso" class="form-label">Fecha de Ingreso</label> <label for="fecha_ingreso" class="form-label">Fecha de Ingreso</label>
<input name="fecha_ingreso" id="fecha_ingreso" type="date" class="form-control" <div class="form-floating-custom">
<input name="fecha_ingreso" id="fecha_ingreso" type="text" class="form-control input-pulse input-gradient"
value="<?= htmlspecialchars($chofer['fecha_ingreso']) ?>"> value="<?= htmlspecialchars($chofer['fecha_ingreso']) ?>">
<label for="fecha_ingreso" class="form-label">Fecha de Ingreso</label>
</div>
</div>
<!-- Cambiar foto -->
<div class="col-md-6 form-group-animated">
<label for="foto" class="form-label">Nueva Foto</label>
<div class="form-floating-custom">
<input name="foto" id="foto" type="file" class="form-control no-animation no-border-style" accept="image/*">
</div> </div>
</div> </div>
<!-- Foto actual --> <!-- Foto actual -->
<div class="mb-3"> <div class="mb-3 form-group-animated">
<label class="form-label">Foto Actual</label><br> <label class="form-label">Foto Actual</label><br>
<?php if (!empty($chofer['foto_url'])): ?> <?php if (!empty($chofer['foto_url'])): ?>
<img src="<?= htmlspecialchars($chofer['foto_url']) ?>" alt="Foto Chofer" width="100" class="mb-2"> <img src="<?= htmlspecialchars($chofer['foto_url']) ?>" alt="Foto Chofer" width="100" class="mb-2">
@@ -147,21 +288,22 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
<!-- Cambiar foto -->
<div class="mb-3">
<label for="foto" class="form-label">Nueva Foto</label>
<input name="foto" id="foto" type="file" class="form-control" accept="image/*">
</div>
<!-- Activo --> <!-- Activo -->
<div class="form-check mb-4"> <div class="col-md-2 form-group-animated">
<div class="form-check">
<div class="form-floating-custom">
<input name="status" id="status" type="checkbox" class="form-check-input" <input name="status" id="status" type="checkbox" class="form-check-input"
<?= $chofer['status'] == 1 ? 'checked' : '' ?>> <?= $chofer['status'] == 1 ? 'checked' : '' ?>>
<label for="status" class="form-check-label">Activo</label> <label for="status" class="form-check-label">Activo</label>
</div> </div>
</div>
</div>
</div>
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button> <button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/choferes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/choferes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -64,6 +64,51 @@ if ($tipoUsuario === 'agente_aduanal') {
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
.hide { display: none !important; } .hide { display: none !important; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
</style> </style>
</head> </head>
<body> <body>
@@ -73,15 +118,15 @@ if ($tipoUsuario === 'agente_aduanal') {
<div class="row g-3"> <div class="row g-3">
<!-- Nuevo Estado --> <!-- Nuevo Estado -->
<div class="col-md"> <div class="col-md form-group-animated">
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto"> <div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto">
<form id="estadoForm"> <form id="estadoForm">
<h4 class="mb-4 text-dark"> Nuevo Estado</h4><br> <h4 class="mb-4 text-dark"> Nuevo Estado</h4><br>
<!-- País --> <!-- País -->
<div class="col-md-12"> <div class="col-md-12 form-group-animated">
<label for="paisEstado" class="form-label">País *</label> <label for="paisEstado" class="form-label">País *</label>
<select id="paisEstado" name="pais" class="form-select" required> <select id="paisEstado" name="pais" class="form-select" required>
<option value="">Selecciona país</option> <option value="">-- Selecciona un país --</option>
<?php foreach($paises as $p): ?> <?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"> <option value="<?= $p['id_pais'] ?>">
<?= htmlspecialchars($p['nombre']) ?> <?= htmlspecialchars($p['nombre']) ?>
@@ -91,12 +136,12 @@ if ($tipoUsuario === 'agente_aduanal') {
</div><br><br><br> </div><br><br><br>
<!-- Estado --> <!-- Estado -->
<div class="col-md-12"> <div class="col-md-12 form-group-animated">
<label for="entidadEstado" class="form-label">Entidad / Provincia *</label> <label for="entidadEstado" class="form-label">Entidad / Provincia *</label>
<input id="entidadEstado" name="entidad" class="form-control" required> <input id="entidadEstado" name="entidad" class="form-control" required>
</div><br><br> </div><br><br>
<div class="col-md-4 d-flex align-items-end"> <div class="col-md-4 d-flex align-items-end form-group-animated">
<button type="submit" class="btn btn-success W-100 btn-animated"> <button type="submit" class="btn btn-success W-100 btn-animated">
<i class="fas fa-plus"></i> Registrar Estado <i class="fas fa-plus"></i> Registrar Estado
</button> </button>
@@ -108,15 +153,15 @@ if ($tipoUsuario === 'agente_aduanal') {
</div> </div>
<!-- Nueva Ciudad --> <!-- Nueva Ciudad -->
<div class="col-md"> <div class="col-md form-group-animated">
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto"> <div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto">
<form id="ciudadForm" action="/IMPORTADORES/locaciones/guadarCiudad" method="POST" enctype="multipart/form-data"> <form id="ciudadForm" action="/IMPORTADORES/locaciones/guadarCiudad" method="POST" enctype="multipart/form-data">
<h4 class="mb-4 text-dark"> Nueva Ciudad</h4> <h4 class="mb-4 text-dark"> Nueva Ciudad</h4>
<!-- País --> <!-- País -->
<div class="col-md-12"> <div class="col-md-12 form-group-animated">
<label for="paisCiudad" class="form-label">País *</label> <label for="paisCiudad" class="form-label">País *</label>
<select id="paisCiudad" name="pais" class="form-select" required> <select id="paisCiudad" name="pais" class="form-select" required>
<option value="">Selecciona país</option> <option value="">-- Selecciona un país --</option>
<?php foreach($paises as $p): ?> <?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"> <option value="<?= $p['id_pais'] ?>">
<?= htmlspecialchars($p['nombre']) ?> <?= htmlspecialchars($p['nombre']) ?>
@@ -126,7 +171,7 @@ if ($tipoUsuario === 'agente_aduanal') {
</div><br> </div><br>
<!-- Estado --> <!-- Estado -->
<div class="col-md-12"> <div class="col-md-12 form-group-animated">
<label for="entidadCiudad" class="form-label">Entidad / Provincia *</label> <label for="entidadCiudad" class="form-label">Entidad / Provincia *</label>
<select id="estadoCiudad" name="entidad" class="form-select" required disabled> <select id="estadoCiudad" name="entidad" class="form-select" required disabled>
<option value="">Primero país…</option> <option value="">Primero país…</option>
@@ -134,12 +179,12 @@ if ($tipoUsuario === 'agente_aduanal') {
</div><br> </div><br>
<!-- Ciudad --> <!-- Ciudad -->
<div class="col-md-12"> <div class="col-md-12 form-group-animated">
<label for="nombreCiudad" class="form-label">Ciudad *</label> <label for="nombreCiudad" class="form-label">Ciudad *</label>
<input id="nombreCiudad" name="ciudad" class="form-control" required> <input id="nombreCiudad" name="ciudad" class="form-control" required>
</div><br> </div><br>
<div class="col-md-4 d-flex align-items-end"> <div class="col-md-4 d-flex align-items-end form-group-animated">
<button type="submit" class="btn btn-success W-100 btn-animated"> <button type="submit" class="btn btn-success W-100 btn-animated">
<i class="fas fa-plus"></i> Registrar Ciudad <i class="fas fa-plus"></i> Registrar Ciudad
</button> </button>
@@ -161,7 +206,7 @@ if ($tipoUsuario === 'agente_aduanal') {
fetch(`/IMPORTADORES/locaciones/estados?pais=${paisId}`) fetch(`/IMPORTADORES/locaciones/estados?pais=${paisId}`)
.then(response => response.json()) .then(response => response.json())
.then(estados => { .then(estados => {
selectElement.innerHTML = '<option value="">Selecciona estado</option>'; selectElement.innerHTML = '<option value="">-- Selecciona un estado --</option>';
estados.forEach(estado => { estados.forEach(estado => {
const option = new Option(estado.nombre, estado.id_estado); const option = new Option(estado.nombre, estado.id_estado);
selectElement.add(option); selectElement.add(option);
@@ -182,7 +227,7 @@ if ($tipoUsuario === 'agente_aduanal') {
if (paisId) { if (paisId) {
cargarEstados(paisId, estadoSelect); cargarEstados(paisId, estadoSelect);
} else { } else {
estadoSelect.innerHTML = '<option value="">Primero selecciona país</option>'; estadoSelect.innerHTML = '<option value="">Primero selecciona un país...</option>';
estadoSelect.disabled = true; estadoSelect.disabled = true;
} }
}); });
@@ -200,20 +245,12 @@ if ($tipoUsuario === 'agente_aduanal') {
// Validación en frontend // Validación en frontend
if (!paisValue) { if (!paisValue) {
Swal.fire({ Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un país' });
icon: 'warning',
title: 'Campo requerido',
text: 'Debe seleccionar un país'
});
return; return;
} }
if (!entidadValue) { if (!entidadValue) {
Swal.fire({ Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe ingresar el nombre del estado' });
icon: 'warning',
title: 'Campo requerido',
text: 'Debe ingresar el nombre del estado'
});
return; return;
} }
@@ -241,29 +278,15 @@ if ($tipoUsuario === 'agente_aduanal') {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
Swal.fire({ Swal.fire({ icon: 'success', title: '¡Éxito!', text: data.message, timer: 2000, showConfirmButton: false });
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
});
this.reset(); this.reset();
} else { } else {
Swal.fire({ Swal.fire({ icon: 'error', title: 'Error', text: data.message });
icon: 'error',
title: 'Error',
text: data.message
});
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
Swal.fire({ Swal.fire({ icon: 'error', title: 'Error', text: 'Ocurrió un error al procesar la solicitud' });
icon: 'error',
title: 'Error',
text: 'Ocurrió un error al procesar la solicitud'
});
}) })
.finally(() => { .finally(() => {
submitBtn.innerHTML = originalText; submitBtn.innerHTML = originalText;
@@ -289,38 +312,62 @@ if ($tipoUsuario === 'agente_aduanal') {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
Swal.fire({ Swal.fire({ icon: 'success', title: '¡Éxito!', text: data.message, timer: 2000, showConfirmButton: false });
icon: 'success',
title: '¡Éxito!',
text: data.message,
timer: 2000,
showConfirmButton: false
});
this.reset(); this.reset();
// Resetear el select de estados // Resetear el select de estados
document.getElementById('estadoCiudad').innerHTML = '<option value="">Primero selecciona país</option>'; document.getElementById('estadoCiudad').innerHTML = '<option value="">Primero selecciona país</option>';
document.getElementById('estadoCiudad').disabled = true; document.getElementById('estadoCiudad').disabled = true;
} else { } else {
Swal.fire({ Swal.fire({ icon: 'error', title: 'Error', text: data.message });
icon: 'error',
title: 'Error',
text: data.message
});
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
Swal.fire({ Swal.fire({ icon: 'error', title: 'Error', text: 'Ocurrió un error al procesar la solicitud' });
icon: 'error',
title: 'Error',
text: 'Ocurrió un error al procesar la solicitud'
});
}) })
.finally(() => { .finally(() => {
submitBtn.innerHTML = originalText; submitBtn.innerHTML = originalText;
submitBtn.disabled = false; submitBtn.disabled = false;
}); });
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Validación del formulario
document.getElementById('formAltaPatente').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script> </script>
</body> </body>

View File

@@ -26,7 +26,7 @@
if ($esDashboard): ?> if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?> Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?> <?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a> <a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

View File

@@ -26,7 +26,7 @@
if ($esDashboard): ?> if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?> Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?> <?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a> <a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

View File

@@ -26,7 +26,7 @@
if ($esDashboard): ?> if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?> Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?> <?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a> <a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

View File

@@ -172,7 +172,7 @@ function obtenerUrlPermiso($permiso) {
default => '/IMPORTADORES/importadores/dashboard' default => '/IMPORTADORES/importadores/dashboard'
}; };
?> ?>
<a href="<?= $urlRegreso ?>" class="btn btn-outline-light btn-sm" style="margin: 0 0 0 25px;"> <a href="<?= $urlRegreso ?>" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated" style="margin: 0 0 0 25px;">
← Panel Principal ← Panel Principal
</a> </a>
</div> </div>

View File

@@ -24,7 +24,7 @@
if ($esDashboard): ?> if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?> Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?> <?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a> <a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

View File

@@ -89,6 +89,51 @@ if ($tipoUsuario === 'agente_aduanal') {
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; .btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
</style> </style>
</head> </head>
<body> <body>
@@ -119,67 +164,67 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Pestaña General --> <!-- Pestaña General -->
<div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab"> <div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab">
<p style="font-weight: bold;">Datos de la Patente / Aduana</p> <p style="font-weight: bold;">Datos de la Patente / Aduana</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="aduana" class="col-md-2 col-form-label">Aduana</label> <label for="aduana" class="col-md-2 col-form-label">Aduana</label>
<div class="col-md-3"> <div class="col-md-3 form-group-animated">
<input name="aduana" id="aduana" type="text" maxlength="3" class="form-control" required> <input name="aduana" id="aduana" type="text" maxlength="3" class="form-control" required>
</div> </div>
<label for="aduana" class="col-md-2 col-form-label">[ Sin Sección ]</label> <label for="aduana" class="col-md-2 col-form-label">[ Sin Sección ]</label>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="patente" class="col-md-2 col-form-label">Patente</label> <label for="patente" class="col-md-2 col-form-label">Patente</label>
<div class="col-md-3"> <div class="col-md-3 form-group-animated">
<input name="patente" id="patente" type="text" maxlength="4" class="form-control" required> <input name="patente" id="patente" type="text" maxlength="4" class="form-control" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="agente_aduanal" class="col-md-2 col-form-label">Agente Aduanal</label> <label for="agente_aduanal" class="col-md-2 col-form-label">Agente Aduanal</label>
<div class="col-md-10"> <div class="col-md-10 form-group-animated">
<input name="agente_aduanal" id="agente_aduanal" type="text" class="form-control" required> <input name="agente_aduanal" id="agente_aduanal" type="text" class="form-control" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="rfc" class="col-md-2 col-form-label">RFC</label> <label for="rfc" class="col-md-2 col-form-label">RFC</label>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<input name="rfc" id="rfc" type="text" maxlength="13" class="form-control" required> <input name="rfc" id="rfc" type="text" maxlength="13" class="form-control" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="curp" class="col-md-2 col-form-label">CURP</label> <label for="curp" class="col-md-2 col-form-label">CURP</label>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<input name="curp" id="curp" type="text" maxlength="18" class="form-control" required> <input name="curp" id="curp" type="text" maxlength="18" class="form-control" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="razon_social" class="col-md-2 col-form-label">Razón Social</label> <label for="razon_social" class="col-md-2 col-form-label">Razón Social</label>
<div class="col-md-10"> <div class="col-md-10 form-group-animated">
<input name="razon_social" id="razon_social" type="text" class="form-control" required> <input name="razon_social" id="razon_social" type="text" class="form-control" required>
</div> </div>
</div><hr> </div><hr>
<div class="col mb-3"> <div class="col mb-3 form-group-animated">
<P>Capturar para el llenado de la Manifestación de Valor</P> <P>Capturar para el llenado de la Manifestación de Valor</P>
<p style="font-weight: bold;">Datos del Agente Aduanal:</p> <p style="font-weight: bold;">Datos del Agente Aduanal:</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label> <label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label>
<div class="col-md-9"> <div class="col-md-9">
<input name="mf_nombre" id="mf_nombre" type="text" class="form-control"> <input name="mf_nombre" id="mf_nombre" type="text" class="form-control">
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="mf_paterno" class="col-md-2 col-form-label">Apellido Paterno</label> <label for="mf_paterno" class="col-md-2 col-form-label">Apellido Paterno</label>
<div class="col-md-9"> <div class="col-md-9 form-group-animated">
<input name="mf_paterno" id="mf_paterno" type="text" class="form-control"> <input name="mf_paterno" id="mf_paterno" type="text" class="form-control">
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="mf_materno" class="col-md-2 col-form-label">Apellido Materno</label> <label for="mf_materno" class="col-md-2 col-form-label">Apellido Materno</label>
<div class="col-md-9"> <div class="col-md-9 form-group-animated">
<input name="mf_materno" id="mf_materno" type="text" class="form-control"> <input name="mf_materno" id="mf_materno" type="text" class="form-control">
</div> </div>
</div> </div>
@@ -191,107 +236,107 @@ if ($tipoUsuario === 'agente_aduanal') {
<p style="font-weight: bold;">Números de Validación y Folios</p> <p style="font-weight: bold;">Números de Validación y Folios</p>
<!-- Pedimentos --> <!-- Pedimentos -->
<p>Números de Validación para Pedimentos</p> <p>Números de Validación para Pedimentos</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vp_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vp_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vp_inicio" id="vp_inicio" type="text" maxlength="3" class="form-control" value="000"> <input name="vp_inicio" id="vp_inicio" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vp_final" class="col-md-1 col-form-label">Final:</label> <label for="vp_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vp_final" id="vp_final" type="text" maxlength="3" class="form-control" value="000"> <input name="vp_final" id="vp_final" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vp_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vp_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vp_siguiente" id="vp_siguiente" type="text" maxlength="3" class="form-control" value="000"> <input name="vp_siguiente" id="vp_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
</div><hr> </div><hr>
<!-- Pago Electrónico --> <!-- Pago Electrónico -->
<p>Números de Validación para Pago Electrónico</p> <p>Números de Validación para Pago Electrónico</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vpe_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vpe_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vpe_inicio" id="vpe_inicio" type="text" maxlength="3" class="form-control" value="000"> <input name="vpe_inicio" id="vpe_inicio" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vpe_final" class="col-md-1 col-form-label">Final:</label> <label for="vpe_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vpe_final" id="vpe_final" type="text" maxlength="3" class="form-control" value="000"> <input name="vpe_final" id="vpe_final" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vpe_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vpe_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vpe_siguiente" id="vpe_siguiente" type="text" maxlength="3" class="form-control" value="000"> <input name="vpe_siguiente" id="vpe_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
</div><hr> </div><hr>
<!-- Avisos Traslado / Plantas-Bodegas --> <!-- Avisos Traslado / Plantas-Bodegas -->
<p>Números de Validación para Avisos Traslado / Plantas-Bodegas</p> <p>Números de Validación para Avisos Traslado / Plantas-Bodegas</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vat_pb_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vat_pb_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vat_pb_inicio" id="vat_pb_inicio" type="text" maxlength="3" class="form-control" value="000"> <input name="vat_pb_inicio" id="vat_pb_inicio" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vat_pb_final" class="col-md-1 col-form-label">Final:</label> <label for="vat_pb_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vat_pb_final" id="vat_pb_final" type="text" maxlength="3" class="form-control" value="000"> <input name="vat_pb_final" id="vat_pb_final" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vat_pb_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vat_pb_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vat_pb_siguiente" id="vat_pb_siguiente" type="text" maxlength="3" class="form-control" value="000"> <input name="vat_pb_siguiente" id="vat_pb_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
</div><hr> </div><hr>
<!-- Validación y Folio de Cartas Cupo --> <!-- Validación y Folio de Cartas Cupo -->
<p>Números de Validación para Cartas Cupo</p> <p>Números de Validación para Cartas Cupo</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vcc_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vcc_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vcc_inicio" id="vcc_inicio" type="text" maxlength="3" class="form-control" value="000"> <input name="vcc_inicio" id="vcc_inicio" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vcc_final" class="col-md-1 col-form-label">Final:</label> <label for="vcc_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vcc_final" id="vcc_final" type="text" maxlength="3" class="form-control" value="000"> <input name="vcc_final" id="vcc_final" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vcc_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vcc_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vcc_siguiente" id="vcc_siguiente" type="text" maxlength="3" class="form-control" value="000"> <input name="vcc_siguiente" id="vcc_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
</div><hr> </div><hr>
<!-- Avisos Electrónicos --> <!-- Avisos Electrónicos -->
<p>Números de Validación para Avisos Electrónicos</p> <p>Números de Validación para Avisos Electrónicos</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vae_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vae_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vae_inicio" id="vae_inicio" type="text" maxlength="3" class="form-control" value="000"> <input name="vae_inicio" id="vae_inicio" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vae_final" class="col-md-1 col-form-label">Final:</label> <label for="vae_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vae_final" id="vae_final" type="text" maxlength="3" class="form-control" value="000"> <input name="vae_final" id="vae_final" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vae_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vae_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vae_siguiente" id="vae_siguiente" type="text" maxlength="3" class="form-control" value="000"> <input name="vae_siguiente" id="vae_siguiente" type="text" maxlength="3" class="form-control" value="000">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div style="display: flex; justify-content: right;"> <div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Registrar</button> <button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Registrar</button>
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div> </div>
@@ -421,6 +466,43 @@ if ($tipoUsuario === 'agente_aduanal') {
// SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO // SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO
this.submit(); this.submit();
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Validación del formulario
document.getElementById('formAltaPatente').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script> </script>
</body> </body>

View File

@@ -86,6 +86,51 @@ if ($tipoUsuario === 'agente_aduanal') {
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; .btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
</style> </style>
</head> </head>
<body> <body>
@@ -118,75 +163,75 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Pestaña General --> <!-- Pestaña General -->
<div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab"> <div class="tab-pane fade show active" id="general" role="tabpanel" aria-labelledby="general-tab">
<p style="font-weight: bold;">Datos de la Patente / Aduana</p> <p style="font-weight: bold;">Datos de la Patente / Aduana</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="aduana" class="col-md-2 col-form-label">Aduana</label> <label for="aduana" class="col-md-2 col-form-label">Aduana</label>
<div class="col-md-3"> <div class="col-md-3 form-group-animated">
<input name="aduana" id="aduana" type="text" maxlength="3" class="form-control" <input name="aduana" id="aduana" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars($agente['aduana'] ?? '') ?>" required> value="<?= htmlspecialchars($agente['aduana'] ?? '') ?>" required>
</div> </div>
<label for="aduana" class="col-md-2 col-form-label">[Sin Sección]</label> <label for="aduana" class="col-md-2 col-form-label">[Sin Sección]</label>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="patente" class="col-md-2 col-form-label">Patente</label> <label for="patente" class="col-md-2 col-form-label">Patente</label>
<div class="col-md-3"> <div class="col-md-3 form-group-animated">
<input name="patente" id="patente" type="text" maxlength="4" class="form-control" <input name="patente" id="patente" type="text" maxlength="4" class="form-control"
value="<?= htmlspecialchars($agente['patente'] ?? '') ?>" required> value="<?= htmlspecialchars($agente['patente'] ?? '') ?>" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="agente_aduanal" class="col-md-2 col-form-label">Agente Aduanal</label> <label for="agente_aduanal" class="col-md-2 col-form-label">Agente Aduanal</label>
<div class="col-md-10"> <div class="col-md-10 form-group-animated">
<input name="agente_aduanal" id="agente_aduanal" type="text" class="form-control" <input name="agente_aduanal" id="agente_aduanal" type="text" class="form-control"
value="<?= htmlspecialchars($agente['agente_aduanal'] ?? '') ?>" required> value="<?= htmlspecialchars($agente['agente_aduanal'] ?? '') ?>" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="rfc" class="col-md-2 col-form-label">RFC</label> <label for="rfc" class="col-md-2 col-form-label">RFC</label>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<input name="rfc" id="rfc" type="text" maxlength="13" class="form-control" <input name="rfc" id="rfc" type="text" maxlength="13" class="form-control"
value="<?= htmlspecialchars($agente['rfc'] ?? '') ?>" required> value="<?= htmlspecialchars($agente['rfc'] ?? '') ?>" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="curp" class="col-md-2 col-form-label">CURP</label> <label for="curp" class="col-md-2 col-form-label">CURP</label>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<input name="curp" id="curp" type="text" maxlength="18" class="form-control" <input name="curp" id="curp" type="text" maxlength="18" class="form-control"
value="<?= htmlspecialchars($agente['curp'] ?? '') ?>" required> value="<?= htmlspecialchars($agente['curp'] ?? '') ?>" required>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="razon_social" class="col-md-2 col-form-label">Razón Social</label> <label for="razon_social" class="col-md-2 col-form-label">Razón Social</label>
<div class="col-md-10"> <div class="col-md-10 form-group-animated">
<input name="razon_social" id="razon_social" type="text" class="form-control" <input name="razon_social" id="razon_social" type="text" class="form-control"
value="<?= htmlspecialchars($agente['razon_social'] ?? '') ?>" required> value="<?= htmlspecialchars($agente['razon_social'] ?? '') ?>" required>
</div> </div>
</div><hr> </div><hr>
<div class="col mb-3"> <div class="col mb-3 form-group-animated">
<P>Capturar para el llenado de la Manifestación de Valor</P> <P>Capturar para el llenado de la Manifestación de Valor</P>
<p style="font-weight: bold;">Datos del Agente Aduanal:</p> <p style="font-weight: bold;">Datos del Agente Aduanal:</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label> <label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label>
<div class="col-md-9"> <div class="col-md-9 form-group-animated">
<input name="mf_nombre" id="mf_nombre" type="text" class="form-control" <input name="mf_nombre" id="mf_nombre" type="text" class="form-control"
value="<?= htmlspecialchars($agente['mf_nombre'] ?? '') ?>"> value="<?= htmlspecialchars($agente['mf_nombre'] ?? '') ?>">
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="mf_paterno" class="col-md-2 col-form-label">Apellido Paterno</label> <label for="mf_paterno" class="col-md-2 col-form-label">Apellido Paterno</label>
<div class="col-md-9"> <div class="col-md-9 form-group-animated">
<input name="mf_paterno" id="mf_paterno" type="text" class="form-control" <input name="mf_paterno" id="mf_paterno" type="text" class="form-control"
value="<?= htmlspecialchars($agente['mf_paterno'] ?? '') ?>"> value="<?= htmlspecialchars($agente['mf_paterno'] ?? '') ?>">
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<label for="mf_materno" class="col-md-2 col-form-label">Apellido Materno</label> <label for="mf_materno" class="col-md-2 col-form-label">Apellido Materno</label>
<div class="col-md-9"> <div class="col-md-9 form-group-animated">
<input name="mf_materno" id="mf_materno" type="text" class="form-control" <input name="mf_materno" id="mf_materno" type="text" class="form-control"
value="<?= htmlspecialchars($agente['mf_materno'] ?? '') ?>"> value="<?= htmlspecialchars($agente['mf_materno'] ?? '') ?>">
</div> </div>
@@ -199,22 +244,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<p style="font-weight: bold;">Números de Validación y Folios</p> <p style="font-weight: bold;">Números de Validación y Folios</p>
<!-- Pedimentos --> <!-- Pedimentos -->
<p>Números de Validación para Pedimentos</p> <p>Números de Validación para Pedimentos</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vp_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vp_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vp_inicio" id="vp_inicio" type="text" maxlength="3" class="form-control" <input name="vp_inicio" id="vp_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_inicio'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_inicio'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vp_final" class="col-md-1 col-form-label">Final:</label> <label for="vp_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vp_final" id="vp_final" type="text" maxlength="3" class="form-control" <input name="vp_final" id="vp_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_final'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_final'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vp_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vp_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vp_siguiente" id="vp_siguiente" type="text" maxlength="3" class="form-control" <input name="vp_siguiente" id="vp_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_siguiente'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vp_siguiente'] ?? '')) ?>">
</div> </div>
@@ -222,22 +267,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Pago Electrónico --> <!-- Pago Electrónico -->
<p>Números de Validación para Pago Electrónico</p> <p>Números de Validación para Pago Electrónico</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vpe_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vpe_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vpe_inicio" id="vpe_inicio" type="text" maxlength="3" class="form-control" <input name="vpe_inicio" id="vpe_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_inicio'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_inicio'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vpe_final" class="col-md-1 col-form-label">Final:</label> <label for="vpe_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vpe_final" id="vpe_final" type="text" maxlength="3" class="form-control" <input name="vpe_final" id="vpe_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_final'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_final'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vpe_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vpe_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vpe_siguiente" id="vpe_siguiente" type="text" maxlength="3" class="form-control" <input name="vpe_siguiente" id="vpe_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_siguiente'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vpe_siguiente'] ?? '')) ?>">
</div> </div>
@@ -245,22 +290,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Avisos Traslado / Plantas-Bodegas --> <!-- Avisos Traslado / Plantas-Bodegas -->
<p>Números de Validación para Avisos Traslado / Plantas-Bodegas</p> <p>Números de Validación para Avisos Traslado / Plantas-Bodegas</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vat_pb_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vat_pb_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vat_pb_inicio" id="vat_pb_inicio" type="text" maxlength="3" class="form-control" <input name="vat_pb_inicio" id="vat_pb_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_inicio'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_inicio'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vat_pb_final" class="col-md-1 col-form-label">Final:</label> <label for="vat_pb_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vat_pb_final" id="vat_pb_final" type="text" maxlength="3" class="form-control" <input name="vat_pb_final" id="vat_pb_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_final'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_final'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vat_pb_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vat_pb_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vat_pb_siguiente" id="vat_pb_siguiente" type="text" maxlength="3" class="form-control" <input name="vat_pb_siguiente" id="vat_pb_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_siguiente'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vat_pb_siguiente'] ?? '')) ?>">
</div> </div>
@@ -268,22 +313,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Validación y Folio de Cartas Cupo --> <!-- Validación y Folio de Cartas Cupo -->
<p>Números de Validación para Cartas Cupo</p> <p>Números de Validación para Cartas Cupo</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vcc_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vcc_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vcc_inicio" id="vcc_inicio" type="text" maxlength="3" class="form-control" <input name="vcc_inicio" id="vcc_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_inicio'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_inicio'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vcc_final" class="col-md-1 col-form-label">Final:</label> <label for="vcc_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vcc_final" id="vcc_final" type="text" maxlength="3" class="form-control" <input name="vcc_final" id="vcc_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_final'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_final'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vcc_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vcc_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vcc_siguiente" id="vcc_siguiente" type="text" maxlength="3" class="form-control" <input name="vcc_siguiente" id="vcc_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_siguiente'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vcc_siguiente'] ?? '')) ?>">
</div> </div>
@@ -291,22 +336,22 @@ if ($tipoUsuario === 'agente_aduanal') {
<!-- Avisos Electrónicos --> <!-- Avisos Electrónicos -->
<p>Números de Validación para Avisos Electrónicos</p> <p>Números de Validación para Avisos Electrónicos</p>
<div class="row mb-3"> <div class="row mb-3 form-group-animated">
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vae_inicio" class="col-md-1 col-form-label">Inicio:</label> <label for="vae_inicio" class="col-md-1 col-form-label">Inicio:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vae_inicio" id="vae_inicio" type="text" maxlength="3" class="form-control" <input name="vae_inicio" id="vae_inicio" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_inicio'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_inicio'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vae_final" class="col-md-1 col-form-label">Final:</label> <label for="vae_final" class="col-md-1 col-form-label">Final:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vae_final" id="vae_final" type="text" maxlength="3" class="form-control" <input name="vae_final" id="vae_final" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_final'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_final'] ?? '')) ?>">
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<label for="vae_siguiente" class="col-md-1 col-form-label">Siguiente:</label> <label for="vae_siguiente" class="col-md-1 col-form-label">Siguiente:</label>
<div class="col-md-1"> <div class="col-md-1 form-group-animated">
<input name="vae_siguiente" id="vae_siguiente" type="text" maxlength="3" class="form-control" <input name="vae_siguiente" id="vae_siguiente" type="text" maxlength="3" class="form-control"
value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_siguiente'] ?? '')) ?>"> value="<?= htmlspecialchars(sprintf('%03d', $agente['vae_siguiente'] ?? '')) ?>">
</div> </div>
@@ -314,7 +359,7 @@ if ($tipoUsuario === 'agente_aduanal') {
</div> </div>
</div> </div>
<div style="display: flex; justify-content: right;"> <div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-success">💾 Guardar</button> <button type="submit" class="btn btn-success">💾 Guardar</button>
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div> </div>

View File

@@ -110,8 +110,8 @@ if ($tipoUsuario === 'agente_aduanal') {
<td><?= htmlspecialchars($aa['curp']) ?></td> <td><?= htmlspecialchars($aa['curp']) ?></td>
<td><?= htmlspecialchars($aa['razon_social']) ?></td> <td><?= htmlspecialchars($aa['razon_social']) ?></td>
<td> <td>
<a href="/IMPORTADORES/patente/editar?id=<?= $aa['id_agente'] ?>" class="btn btn-sm btn-primary">✏️</a> <a href="/IMPORTADORES/patente/editar?id=<?= $aa['id_agente'] ?>" class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
<button class="btn btn-sm btn-danger" onclick="confirmDeleteChofer(<?= $aa['id_agente'] ?>)">🗑️</button> <button class="btn btn-sm btn-danger mt-auto w-auto btn-animated" onclick="confirmDeleteChofer(<?= $aa['id_agente'] ?>)">🗑️</button>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@@ -147,13 +147,13 @@ if ($tipoUsuario === 'agente_aduanal') {
}); });
<?php if(isset($_GET['deleted'])): ?> <?php if(isset($_GET['deleted'])): ?>
Swal.fire('¡Hecho!','Agente eliminado.','success'); Swal.fire('¡Hecho!','Patente eliminada.','success');
<?php endif; ?> <?php endif; ?>
<?php if(isset($_GET['created'])): ?> <?php if(isset($_GET['created'])): ?>
Swal.fire('¡Listo!','Agente creado.','success'); Swal.fire('¡Listo!','Patente creado.','success');
<?php endif; ?> <?php endif; ?>
<?php if(isset($_GET['updated'])): ?> <?php if(isset($_GET['updated'])): ?>
Swal.fire('¡Listo!','Agente actualizado.','success'); Swal.fire('¡Listo!','Patente actualizada.','success');
<?php endif; ?> <?php endif; ?>
</script> </script>

View File

@@ -28,8 +28,11 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Choices.js CSS --> <!-- Choices.js CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
<!-- SweetAlert2 --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales --> <!-- Animate.css para animaciones adicionales -->
@@ -67,6 +70,51 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
.form-label { font-weight: 500; } .form-label { font-weight: 500; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
</style> </style>
</head> </head>
<body> <body>
@@ -87,23 +135,23 @@
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow"> Alta de Producto Frecuente</h4> <h4 class="mb-4 animate__animated animate__fadeInDown title_glow"> Alta de Producto Frecuente</h4>
<div class="card p-4 shadow-sm bg-white card-hover position-relative h-auto"> <div class="card p-4 shadow-sm bg-white card-hover position-relative h-auto">
<form id="altaProductoFrecuenteForm" action="/IMPORTADORES/productos_frecuentes/guardar" method="POST"> <form id="altaProductoFrecuenteForm" action="/IMPORTADORES/productos_frecuentes/guardar" method="POST">
<div class="row g-3"> <div class="row g-3 form-group-animated">
<!-- Sinónimo, Fracción, NICO --> <!-- Sinónimo, Fracción, NICO -->
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="sinonimo" class="form-label">Sinónimo *</label> <label for="sinonimo" class="form-label">Sinónimo *</label>
<input type="text" id="sinonimo" name="sinonimo" class="form-control" required> <input type="text" id="sinonimo" name="sinonimo" class="form-control" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="fraccion" class="form-label">Fracción *</label> <label for="fraccion" class="form-label">Fracción *</label>
<input type="text" id="fraccion" name="fraccion" class="form-control" required> <input type="text" id="fraccion" name="fraccion" class="form-control" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="nico" class="form-label">NICO *</label> <label for="nico" class="form-label">NICO *</label>
<input type="text" id="nico" name="nico" class="form-control" required> <input type="text" id="nico" name="nico" class="form-control" required>
</div> </div>
<!-- Número de Parte / Proveedor --> <!-- Número de Parte / Proveedor -->
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<label for="numero_parte" class="form-label">Número de Parte</label> <label for="numero_parte" class="form-label">Número de Parte</label>
<input type="text" id="numero_parte" name="numero_parte" class="form-control"> <input type="text" id="numero_parte" name="numero_parte" class="form-control">
</div> </div>
@@ -115,7 +163,7 @@
</div> </div>
<!-- Descripción --> <!-- Descripción -->
<div class="col-12"> <div class="col-12 form-group-animated">
<label for="descripcion" class="form-label">Descripción</label> <label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"></textarea> <textarea id="descripcion" name="descripcion" class="form-control" rows="2"></textarea>
</div> </div>
@@ -135,7 +183,7 @@
</select> </select>
</div> </div>
<!-- Uso de la Mercancía --> <!-- Uso de la Mercancía -->
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="uso_mercancia" class="form-label">Uso de la Mercancía</label> <label for="uso_mercancia" class="form-label">Uso de la Mercancía</label>
<input type="text" id="uso_mercancia" name="uso_mercancia" class="form-control"> <input type="text" id="uso_mercancia" name="uso_mercancia" class="form-control">
</div> </div>
@@ -182,21 +230,20 @@
<option value="<?= $val ?>"><?= htmlspecialchars($lab) ?></option> <option value="<?= $val ?>"><?= htmlspecialchars($lab) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<label for="tipo_mercancia" class="form-label">Tipo de Mercancía</label> <label for="tipo_mercancia" class="form-label">Tipo de Mercancía</label>
<input type="text" id="tipo_mercancia" name="tipo_mercancia" class="form-control"> <input type="text" id="tipo_mercancia" name="tipo_mercancia" class="form-control">
</div> </div>
<!-- Checkboxes --> <!-- Checkboxes -->
<div class="col-md-3"> <div class="col-md-3 form-group-animated">
<div class="form-check mt-2"> <div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="certificado_origen" name="certificado_origen" value="1"> <input class="form-check-input" type="checkbox" id="certificado_origen" name="certificado_origen" value="1">
<label class="form-check-label" for="certificado_origen">Certificado de Origen</label> <label class="form-check-label" for="certificado_origen">Certificado de Origen</label>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3 form-group-animated">
<div class="form-check mt-2"> <div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="documento_en_original" name="documento_en_original" value="1"> <input class="form-check-input" type="checkbox" id="documento_en_original" name="documento_en_original" value="1">
<label class="form-check-label" for="documento_en_original">Documento en Original</label> <label class="form-check-label" for="documento_en_original">Documento en Original</label>
@@ -209,7 +256,7 @@
<input type="hidden" name="frecuencia_uso" value="1"> <input type="hidden" name="frecuencia_uso" value="1">
<!-- Botones --> <!-- Botones -->
<div class="col-12 text-end mt-4"> <div class="col-12 text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Producto</button> <button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Producto</button>
<a href="/IMPORTADORES/productos_frecuentes" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/productos_frecuentes" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div> </div>
@@ -218,10 +265,12 @@
</div> </div>
</div> </div>
<!-- JS --> <!-- Choices.js JS -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
function fetchJsonOrThrow(url) { function fetchJsonOrThrow(url) {
@@ -269,7 +318,8 @@
paisData.results.forEach(item => { paisData.results.forEach(item => {
[poSel, pcSel].forEach(sel => { [poSel, pcSel].forEach(sel => {
const o = document.createElement('option'); const o = document.createElement('option');
o.value = item.id; o.textContent = item.text; o.value = item.id;
o.textContent = item.text;
sel.appendChild(o); sel.appendChild(o);
}); });
}); });
@@ -284,12 +334,13 @@
}); });
}); });
// Después de inicializar Choices.js: // Después de inicializar Choices.js:
const prefEl = document.getElementById('preferencia'); const prefEl = document.getElementById('preferencia');
const critEl = document.getElementById('criterio_preferencia'); const critEl = document.getElementById('criterio_preferencia');
const critChoice = critEl._choices; const critChoice = critEl._choices;
function updateCriterio() { if (critChoice) {
function updateCriterio() {
if (prefEl.value === 'PROSEC') { if (prefEl.value === 'PROSEC') {
critEl.disabled = false; critEl.disabled = false;
critChoice.enable(); critChoice.enable();
@@ -298,11 +349,11 @@ function updateCriterio() {
critChoice.disable(); critChoice.disable();
critEl.disabled = true; critEl.disabled = true;
} }
} }
prefEl.addEventListener('change', updateCriterio);
updateCriterio();
prefEl.addEventListener('change', updateCriterio);
updateCriterio();
}
}) })
.catch(err => { .catch(err => {
@@ -315,7 +366,67 @@ updateCriterio();
}); });
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form_select');
const selects = document.querySelectorAll('select.form_select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// Validación del formulario
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -171,10 +171,10 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script> <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function () {
$('#tablaProductosFrecuentes').DataTable({ $('#tablaProductosFrecuentes').DataTable({
language: { language: {
url: '//cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json' url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
} }
}); });
}); });

View File

@@ -51,6 +51,51 @@
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; .btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
</style> </style>
</head> </head>
<body> <body>
@@ -76,48 +121,48 @@
<div class="card p-4 shadow-sm card-hover position-relative h-auto"> <div class="card p-4 shadow-sm card-hover position-relative h-auto">
<form action="/IMPORTADORES/proveedores/guardar" method="POST" enctype="multipart/form-data" id="formAltaProveedores"> <form action="/IMPORTADORES/proveedores/guardar" method="POST" enctype="multipart/form-data" id="formAltaProveedores">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="Clave" class="form-label">Clave *</label> <label for="Clave" class="form-label">Clave *</label>
<input type="text" name="Clave" id="Clave" class="form-control" maxlength="20" required> <input type="text" name="Clave" id="Clave" class="form-control" maxlength="20" required>
</div> </div>
<div class="col-md-8"> <div class="col-md-8 form-group-animated">
<label for="Nombre" class="form-label">Nombre *</label> <label for="Nombre" class="form-label">Nombre *</label>
<input type="text" name="Nombre" id="Nombre" class="form-control" required> <input type="text" name="Nombre" id="Nombre" class="form-control" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="RFC" class="form-label">RFC *</label> <label for="RFC" class="form-label">RFC *</label>
<input type="text" name="RFC" id="RFC" class="form-control" maxlength="13" required> <input type="text" name="RFC" id="RFC" class="form-control" maxlength="13" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="Ciudad" class="form-label">Ciudad *</label> <label for="Ciudad" class="form-label">Ciudad *</label>
<input type="text" name="Ciudad" id="Ciudad" class="form-control" required> <input type="text" name="Ciudad" id="Ciudad" class="form-control" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="Telefono" class="form-label">Teléfono</label> <label for="Telefono" class="form-label">Teléfono</label>
<input type="tel" name="Telefono" id="Telefono" maxlength="11" class="form-control"> <input type="tel" name="Telefono" id="Telefono" maxlength="11" class="form-control">
</div> </div>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<label for="Pais" class="form-label">País</label> <label for="Pais" class="form-label">País</label>
<input type="text" name="Pais" id="Pais" class="form-control"> <input type="text" name="Pais" id="Pais" class="form-control">
</div> </div>
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<label for="EntidadFederativa" class="form-label">Entidad Federativa</label> <label for="EntidadFederativa" class="form-label">Entidad Federativa</label>
<input type="text" name="EntidadFederativa" id="EntidadFederativa" class="form-control"> <input type="text" name="EntidadFederativa" id="EntidadFederativa" class="form-control">
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="Municipio" class="form-label">Municipio</label> <label for="Municipio" class="form-label">Municipio</label>
<input type="text" name="Municipio" id="Municipio" class="form-control"> <input type="text" name="Municipio" id="Municipio" class="form-control">
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="Colonia" class="form-label">Colonia</label> <label for="Colonia" class="form-label">Colonia</label>
<input type="text" name="Colonia" id="Colonia" class="form-control"> <input type="text" name="Colonia" id="Colonia" class="form-control">
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label for="CodigoPostal" class="form-label">Código Postal</label> <label for="CodigoPostal" class="form-label">Código Postal</label>
<input type="text" name="CodigoPostal" id="CodigoPostal" maxlength="5" class="form-control"> <input type="text" name="CodigoPostal" id="CodigoPostal" maxlength="5" class="form-control">
</div> </div>
</div> </div>
<div class="mt-4 text-end"> <div class="mt-4 text-end form-group-animated">
<a href="/IMPORTADORES/proveedores" class="btn btn-secondary me-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/proveedores" class="btn btn-secondary me-2 mt-auto w-auto btn-animated">Cancelar</a>
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Proveedor</button> <button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Guardar Proveedor</button>
</div> </div>
@@ -269,6 +314,44 @@
// SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO // SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO
this.submit(); this.submit();
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Validación del formulario
document.getElementById('formAltaProveedores').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script> </script>
</body> </body>

View File

@@ -98,8 +98,12 @@ $dos_factores_estado = obtenerEstadoDosFactores();
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow-sm p-3"> <div class="card shadow-sm p-3">
<h4 class="mb-4 text-dark">Cambiar contraseña</h4> <h4 class="mb-4 text-dark">Cambiar contraseña</h4>
<p>Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br><br> <p class="text-muted small flex-grow-1 mb-4">Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br>
<a href="/IMPORTADORES/reset/changePassword" class="btn btn-primary">Cambiar contraseña</a> <form>
<button type="button" class="btn btn-primary btn-sm mt-2 w-40 btn-animated" data-bs-toggle="modal" data-bs-target="#modalConfirmarCambio">
<i class="fas fa-key"></i>Cambiar contraseña
</button>
</form>
</div> </div>
</div> </div>

View File

@@ -179,8 +179,12 @@ $dos_factores_estado = obtenerEstadoDosFactores();
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow-sm p-3"> <div class="card shadow-sm p-3">
<h4 class="mb-4 text-dark">Cambiar contraseña</h4> <h4 class="mb-4 text-dark">Cambiar contraseña</h4>
<p>Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br><br> <p class="text-muted small flex-grow-1 mb-4">Al actualizar tu contraseña, refuerzas la seguridad de tu cuenta. Te recomendamos usar una combinación de letras, números y símbolos para mayor seguridad.</p><br>
<a href="/IMPORTADORES/reset/changePassword" class="btn btn-primary">Cambiar contraseña</a> <form>
<button type="button" class="btn btn-primary btn-sm mt-2 w-40 btn-animated" data-bs-toggle="modal" data-bs-target="#modalConfirmarCambio">
<i class="fas fa-key"></i>Cambiar contraseña
</button>
</form>
</div> </div>
</div> </div>

View File

@@ -58,6 +58,80 @@
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
.hide { display: none !important; } .hide { display: none !important; }
.alert { top: 75px; left: 275px; right: 50px; position: fixed; z-index: 1050; width: calc(100% - 285px); } .alert { top: 75px; left: 275px; right: 50px; position: fixed; z-index: 1050; width: calc(100% - 285px); }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.form-label { font-weight: 500; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style> </style>
</head> </head>
<body> <body>
@@ -84,11 +158,11 @@
<!-- Sección principal --> <!-- Sección principal -->
<div class="row"> <div class="row">
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3 form-group-animated">
<label for="numero_factura" class="form-label">Número de Factura</label> <label for="numero_factura" class="form-label">Número de Factura</label>
<input id="numero_factura" name="numero_factura" type="text" class="form-control" required> <input id="numero_factura" name="numero_factura" type="text" class="form-control" required>
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3 form-group-animated">
<label for="fecha_factura" class="form-label">Fecha de Factura</label> <label for="fecha_factura" class="form-label">Fecha de Factura</label>
<input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required> <input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required>
</div> </div>
@@ -156,7 +230,7 @@
<!-- Valor y vinculación --> <!-- Valor y vinculación -->
<div class="row"> <div class="row">
<div class="col-md-8 mb-3"> <div class="col-md-8 mb-3 form-group-animated">
<label for="valor_factura" class="form-label">Valor Factura</label> <label for="valor_factura" class="form-label">Valor Factura</label>
<input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control" required> <input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control" required>
</div> </div>
@@ -194,9 +268,9 @@
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3 form-group-animated">
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label> <label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*"> <input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control no-animation no-border-style" accept="image/*">
</div> </div>
</div> </div>
@@ -221,9 +295,9 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><input name="partidas[0][descripcion]" class="form-control w-auto" required></td> <td><input name="partidas[0][descripcion]" class="form-control w-auto form-group-animated" required></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td> <td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto form-group-animated" min="0" required></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td> <td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto form-group-animated" min="0" required></td>
<td> <td>
<div class="mb-3"> <div class="mb-3">
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable" required> <select name="partidas[0][unidad_comercial_id]" class="form-select searchable" required>
@@ -234,8 +308,8 @@
</select> </select>
</div> </div>
</td> </td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto" min="0" required></td> <td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto form-group-animated" min="0" required></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td> <td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto form-group-animated" min="0" required></td>
<td> <td>
<div class="mb-3"> <div class="mb-3">
<select name="partidas[0][tasa_preferencial]" class="form-select searchable" required> <select name="partidas[0][tasa_preferencial]" class="form-select searchable" required>
@@ -248,24 +322,28 @@
</select> </select>
</div> </div>
</td> </td>
<td class="hide"><input name="partidas[0][precio_unitario]" type="number" class="form-control w-auto"></td> <td class="hide"><input name="partidas[0][precio_unitario]" type="number" class="form-control w-auto form-group-animated"></td>
<td class="hide"><input name="partidas[0][oma_factura]" class="form-control w-auto"></td> <td class="hide"><input name="partidas[0][oma_factura]" class="form-control w-auto form-group-animated"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td> <td><button type="button" class="btn btn-danger btn-sm remove-row mt-auto w-auto btn-animated">✖️</button></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="form-group-animated">
<button type="button" class="btn btn-secondary btn-sm mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button> <button type="button" class="btn btn-secondary btn-sm mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button>
</div>
<!-- Activo --> <!-- Activo -->
<div class="form-check mt-4"> <div class="form-check mt-4 form-group-animated">
<input id="status" name="status" type="checkbox" class="hide form-check-input" checked> <input id="status" name="status" type="checkbox" class="hide form-check-input" checked>
<label for="status" class="hide form-check-label">Activo</label> <label for="status" class="hide form-check-label">Activo</label>
</div> </div>
<!-- Botones finales --> <!-- Botones finales -->
<div class="col-12 text-end mt-4 form-group-animated"></div>
<button type="submit" class="btn btn-success mt-3 mt-auto w-auto btn-animated">Guardar</button> <button type="submit" class="btn btn-success mt-3 mt-auto w-auto btn-animated">Guardar</button>
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary mt-3 ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary mt-3 ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -512,6 +590,67 @@
}); });
}); });
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form_select');
const selects = document.querySelectorAll('select.form_select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// Validación del formulario
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script> </script>
</body> </body>

View File

@@ -59,6 +59,80 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
.hide { display: none !important; } .hide { display: none !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.form-label { font-weight: 500; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { /*border-color: #198754; background-color: #f8fff9;*/ }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { /*border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9);*/ animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style> </style>
</head> </head>
<body> <body>
@@ -71,12 +145,12 @@
<!-- Datos principales --> <!-- Datos principales -->
<div class="row"> <div class="row">
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3 form-group-animated">
<label for="numero_factura" class="form-label">Número de Factura</label> <label for="numero_factura" class="form-label">Número de Factura</label>
<input id="numero_factura" name="numero_factura" type="text" class="form-control" required <input id="numero_factura" name="numero_factura" type="text" class="form-control" required
value="<?= htmlspecialchars($factura['numero_factura']) ?>"> value="<?= htmlspecialchars($factura['numero_factura']) ?>">
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3 form-group-animated">
<label for="fecha_factura" class="form-label">Fecha de Factura</label> <label for="fecha_factura" class="form-label">Fecha de Factura</label>
<input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required <input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required
value="<?= htmlspecialchars($factura['fecha_factura']) ?>"> value="<?= htmlspecialchars($factura['fecha_factura']) ?>">
@@ -150,7 +224,7 @@
<!-- Valor y Vinculación --> <!-- Valor y Vinculación -->
<div class="row"> <div class="row">
<div class="col-md-8 mb-3"> <div class="col-md-8 mb-3 form-group-animated">
<label for="valor_factura" class="form-label">Valor Factura</label> <label for="valor_factura" class="form-label">Valor Factura</label>
<input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control valor-total" required <input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control valor-total" required
value="<?= htmlspecialchars($factura['valor_factura']) ?>"> value="<?= htmlspecialchars($factura['valor_factura']) ?>">
@@ -193,7 +267,7 @@
</div> </div>
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3">
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label> <label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*"> <input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control no-animation no-border-style" accept="image/*">
</div> </div>
</div> </div>
@@ -219,9 +293,9 @@
<tbody> <tbody>
<?php if (!empty($partidas)): foreach($partidas as $i=>$p): ?> <?php if (!empty($partidas)): foreach($partidas as $i=>$p): ?>
<tr> <tr>
<td><input name="partidas[<?= $i ?>][descripcion]" class="form-control" value="<?= htmlspecialchars($p['descripcion']) ?>"></td> <td><input name="partidas[<?= $i ?>][descripcion]" class="form-control form-group-animated" value="<?= htmlspecialchars($p['descripcion']) ?>"></td>
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td> <td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td> <td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
<td> <td>
<select name="partidas[<?= $i ?>][unidad_comercial_id]" class="form-select searchable"> <select name="partidas[<?= $i ?>][unidad_comercial_id]" class="form-select searchable">
<option value="">-- Unidad --</option> <option value="">-- Unidad --</option>
@@ -232,8 +306,8 @@
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</td> </td>
<td><input name="partidas[<?= $i ?>][valor_factura]" type="number" step="0.01" min="0" class="form-control valor-partida" value="<?= htmlspecialchars($p['valor_factura']) ?>"></td> <td><input name="partidas[<?= $i ?>][valor_factura]" type="number" step="0.01" min="0" class="form-control valor-partida form-group-animated" value="<?= htmlspecialchars($p['valor_factura']) ?>"></td>
<td><input name="partidas[<?= $i ?>][peso_bruto]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['peso_bruto']) ?>"></td> <td><input name="partidas[<?= $i ?>][peso_bruto]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['peso_bruto']) ?>"></td>
<td> <td>
<select name="partidas[<?= $i ?>][tasa_preferencial]" class="form-select searchable"> <select name="partidas[<?= $i ?>][tasa_preferencial]" class="form-select searchable">
<option value="">-- Selecciona --</option> <option value="">-- Selecciona --</option>
@@ -242,15 +316,15 @@
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</td> </td>
<td class="hide"><input name="partidas[<?= $i ?>][precio_unitario]" type="number" class="form-control" value="<?= htmlspecialchars($p['precio_unitario'] ?? '') ?>"></td> <td class="hide"><input name="partidas[<?= $i ?>][precio_unitario]" type="number" class="form-control form-group-animated" value="<?= htmlspecialchars($p['precio_unitario'] ?? '') ?>"></td>
<td class="hide"><input name="partidas[<?= $i ?>][oma_factura]" class="form-control" value="<?= htmlspecialchars($p['oma_factura'] ?? '') ?>"></td> <td class="hide"><input name="partidas[<?= $i ?>][oma_factura]" class="form-control form-group-animated" value="<?= htmlspecialchars($p['oma_factura'] ?? '') ?>"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td> <td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td>
</tr> </tr>
<?php endforeach; else: ?> <?php endforeach; else: ?>
<tr> <tr>
<td><input name="partidas[0][descripcion]" class="form-control"></td> <td><input name="partidas[0][descripcion]" class="form-control form-group-animated"></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control" min="0"></td> <td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control" min="0"></td> <td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
<td> <td>
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable"> <select name="partidas[0][unidad_comercial_id]" class="form-select searchable">
<option value="">-- Unidad --</option> <option value="">-- Unidad --</option>
@@ -259,8 +333,8 @@
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</td> </td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida" min="0"></td> <td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida form-group-animated" min="0"></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control" min="0"></td> <td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
<td> <td>
<select name="partidas[0][tasa_preferencial]" class="form-select searchable"> <select name="partidas[0][tasa_preferencial]" class="form-select searchable">
<option value="">-- Selecciona --</option> <option value="">-- Selecciona --</option>
@@ -276,16 +350,21 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="form-group-animated">
<button type="button" class="btn btn-secondary btn-sm mb-3 mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button> <button type="button" class="btn btn-secondary btn-sm mb-3 mt-auto w-auto btn-animated" id="add-partida"> Agregar partida</button>
</div>
<!-- Activo --> <!-- Activo -->
<div class="form-check mb-4"> <div class="form-check mb-4 form-group-animated">
<input id="status" value=1 name="status" type="hidden" <?= $factura['status']==1?'checked':'' ?>> <input id="status" value=1 name="status" type="hidden" <?= $factura['status']==1?'checked':'' ?>>
<label for="status" class="hide form-check-label">Activo</label> <label for="status" class="hide form-check-label">Activo</label>
</div> </div>
<!-- Botones finales -->
<div class="col-12 text-end mt-4 form-group-animated"></div>
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button> <button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -637,6 +716,67 @@
} }
}); });
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form_select');
const selects = document.querySelectorAll('select.form_select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// Validación del formulario
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
});
});
</script> </script>
</body> </body>

View File

@@ -162,8 +162,8 @@
<!-- Celda de Acciones --> <!-- Celda de Acciones -->
<td> <td>
<a href="/IMPORTADORES/solicitud_importacion/editar?id=<?= $f['id_solicitud'] ?>" <a href="/IMPORTADORES/solicitud_importacion/editar?id=<?= $f['id_solicitud'] ?>"
class="btn btn-sm btn-primary">✏️</a> class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
<button class="btn btn-sm btn-danger" <button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="confirmDelete(<?= $f['id_solicitud'] ?>)">🗑️</button> onclick="confirmDelete(<?= $f['id_solicitud'] ?>)">🗑️</button>
</td> </td>
</tr> </tr>
@@ -435,10 +435,6 @@
} }
}); });
}); });
}); });
</script> </script>

View File

@@ -66,58 +66,50 @@
.modal-dialog { z-index: 10000 !important; position: relative; } .modal-dialog { z-index: 10000 !important; position: relative; }
/* Opcional: Mejorar la apariencia del overlay */ /* Opcional: Mejorar la apariencia del overlay */
.modal-backdrop.show { opacity: 0.5; } .modal-backdrop.show { opacity: 0.5; }
/* ========== ANIMACIONES PARA INPUTS ========== */ /* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
/* 1. Animación al hacer focus */ .form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; } .form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); } .form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 2. Animación al escribir */ .form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
.form-control:not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 3. Efecto de shake en validación */
.form-control.shake { animation: shake 0.5s ease-in-out; }
@keyframes shake { @keyframes shake {
0%, 100% { transform: translateX(0); } 0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); } 25% { transform: translateX(-5px); }
75% { transform: translateX(5px); } 75% { transform: translateX(5px); }
} }
/* 4. Floating labels con animación */ /* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; } .form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; } .form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; } .form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label, .form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } .form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
/* 5. Efecto de pulso en hover */ .input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse { @keyframes inputPulse {
0% { transform: scale(1); } 0% { transform: scale(1); }
50% { transform: scale(1.02); } 50% { transform: scale(1.02); }
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
/* 6. Gradiente animado en el borde */ .input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient { position: relative; overflow: hidden; } .input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
.input-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; } background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:focus::before { left: 100%; } .input-gradient:not(.no-animation):focus::before { left: 100%; }
/* 7. Efecto de brillo en validación exitosa */ .form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
.form-control.valid { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow { @keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } 0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); } 50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } 100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
} }
/* 8. Typing effect simulation */ .input-typing:not(.no-animation) { position: relative; }
.input-typing { position: relative; } .input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; } .input-typing:not(.no-animation):focus::after { opacity: 1; }
.input-typing:focus::after { opacity: 1; }
@keyframes blink { @keyframes blink {
0%, 50% { opacity: 1; } 0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; } 51%, 100% { opacity: 0; }
} }
/* 9. Efecto de ondas */ .input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple { position: relative; overflow: hidden; } .input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
.input-ripple::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(13, 110, 253, 0.2); background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; } .input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
.input-ripple:focus::before { width: 300px; height: 300px; } /* Animación para los campos del formulario */
/* 10. Slide-up animation para los campos */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; } .form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; } .form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; } .form-group-animated:nth-child(2) { animation-delay: 0.2s; }
@@ -127,54 +119,51 @@
.form-group-animated:nth-child(6) { animation-delay: 0.6s; } .form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; } .form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; } .form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } } @keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */ /* ========== ANIMACIONES PARA SELECT ========== */
/* 1. Estilos base para select */ .form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
.form-select {transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; } background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
/* 2. Animación al hacer focus */
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); .form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); } background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
/* 3. Animación cuando tiene valor seleccionado */ .form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
.form-select:not([value=""]):not(:invalid) { border-color: #198754; background-color: #f8fff9; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; } .form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
.form-floating-custom .form-select:focus ~ .form-label, /* Floating labels para select */
.form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } .form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
/* 5. Efecto de shake en validación para select */ transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; } .form-select.shake { animation: shake 0.5s ease-in-out; }
/* 6. Efecto de pulso en hover para select */
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; } .select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
/* 7. Gradiente animado para select */
.select-gradient { position: relative; overflow: hidden; } .select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; } .select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; } .select-gradient:focus::before { left: 100%; }
/* 8. Efecto de brillo en validación exitosa para select */
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; } .form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
/* 9. Efecto de rotación de la flecha al abrir */
.select-arrow-rotate { transition: all 0.3s ease; } .select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); } .select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
/* 10. Efecto de ondas para select */
.select-ripple { position: relative; overflow: hidden; } .select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; .select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; } background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; } .select-ripple:focus::after { width: 300px; height: 300px; }
/* 11. Efecto de scale suave */
.select-scale { transition: transform 0.2s ease; } .select-scale { transition: transform 0.2s ease; }
.select-scale:hover { transform: scale(1.02); } .select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); } .select-scale:focus { transform: scale(1.02) translateY(-2px); }
/* 12. Personalización de opciones (limitado en algunos navegadores) */
.form-select option { padding: 0.5rem; transition: all 0.2s ease; } .form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; } .form-select option:hover { background-color: #f8f9fa; }
/* 13. Indicador de estado */
.select-status { position: relative; } .select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%); .select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; } .select-status.valid::after { background-color: #198754; opacity: 1; } width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; } .select-status:invalid::after { background-color: #dc3545; opacity: 1; }
/* Quitar todo tipo de animación y borde para el campo foto */ /* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-border-style { transition: none !important; border: 1px solid #ccc !important; box-shadow: none !important; background: #fff !important; } .no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:focus { transform: none !important; } .no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style> </style>
</head> </head>
<body> <body>
@@ -185,49 +174,51 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6 form-group-animated"> <div class="col-md-6 form-group-animated">
<label for="" class="form-label">Contenedor *</label>
<div class="form-floating-custom"> <div class="form-floating-custom">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input name="vehiculo" id="vehiculo" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required> <input name="vehiculo" id="vehiculo" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required>
<label for="vehiculo" class="form-label">Contenedor *</label> <label for="vehiculo" class="form-label">Contenedor</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button> <button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6 form-group-animated"> <div class="col-md-6 form-group-animated">
<label for="ident_fiscal" class="form-label">Identificación fiscal *</label>
<div class="form-floating-custom"> <div class="form-floating-custom">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input name="identificador_fiscal" id="ident_fiscal" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required> <input name="identificador_fiscal" id="ident_fiscal" class="form-control input-pulse input-gradient" maxlength="20" placeholder=" " required>
<label for="ident_fiscal" class="form-label">Identificación fiscal *</label> <label for="ident_fiscal" class="form-label">Identificación fiscal</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button> <button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6 form-group-animated"> <div class="col-md-6 form-group-animated">
<label class="form-label">Foto (opcional)</label> <label for="" class="form-label">Transportista</label>
<div class="form-floating-custom">
<select name="transportista_id" id="transportista_id" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un transportista --</option>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>">
<?= htmlspecialchars($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="trasportista_id" class="form-label">Transportista</label>
</div>
</div>
<div class="col-md-6 form-group-animated">
<label for="foto" class="form-label">Foto (opcional)</label>
<div class="form-floating-custom"> <div class="form-floating-custom">
<input type="file" name="foto" id="foto" class="form-control no-animation no-border-style" accept="image/*"> <input type="file" name="foto" id="foto" class="form-control no-animation no-border-style" accept="image/*">
</div> </div>
</div> </div>
<div class="col-md-6 form-group-animated">
<label for="" class="form-label">Transportista</label>
<div class="form-floating-custom">
<select name="id_transportista" id="transportista" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value=""></option>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>">
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio'])) ?>
</option>
<?php endforeach; ?>
</select>
<label for="trasportista" class="form-label">Transportista</label>
</div> </div>
</div> <div class="text-end mt-4 form-group-animated">
</div>
<div class="text-end mt-4">
<button type="submit" class="btn btn-success ms-2 mt-auto w-auto btn-animated">Guardar</button> <button type="submit" class="btn btn-success ms-2 mt-auto w-auto btn-animated">Guardar</button>
<a href="/IMPORTADORES/transportes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/transportes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div> </div>
@@ -272,7 +263,7 @@
const vehiculo = document.getElementById('vehiculo').value.trim(); const vehiculo = document.getElementById('vehiculo').value.trim();
const ident_fiscal = document.getElementById('ident_fiscal').value.trim(); const ident_fiscal = document.getElementById('ident_fiscal').value.trim();
const transportista = document.getElementById('transportista').value; const transportista_id = document.getElementById('transportista_id').value;
const fotoF = document.getElementById('foto').files[0]; const fotoF = document.getElementById('foto').files[0];
if (!vehiculo) { if (!vehiculo) {
@@ -294,10 +285,12 @@
this.submit(); this.submit();
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('.form-control'); const inputs = document.querySelectorAll('input.form-control, select.form-select');
inputs.forEach(input => { inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() { input.addEventListener('input', function() {
if (this.checkValidity()) { if (this.checkValidity()) {
this.classList.remove('shake'); this.classList.remove('shake');
@@ -307,6 +300,7 @@
} }
}); });
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() { input.addEventListener('invalid', function() {
this.classList.add('shake'); this.classList.add('shake');
setTimeout(() => { setTimeout(() => {
@@ -314,6 +308,68 @@
}, 500); }, 500);
}); });
}); });
// Validación del formulario
document.getElementById('formTransCrear').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const form = this;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
} else {
// Mostrar mensaje de error
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
}
});
});
// Manejo específico para selects
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('.form-select');
selects.forEach(select => {
// Manejar cambios en el select
select.addEventListener('change', function() {
updateSelectState(this);
});
// Estado inicial
updateSelectState(select);
});
function updateSelectState(select) {
// Actualizar el atributo value para que el CSS pueda detectarlo
select.setAttribute('value', select.value);
// Agregar/quitar clase valid basado en si hay valor seleccionado
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
// Solo agregar invalid si el campo es required y se ha intentado enviar
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
}); });
</script> </script>

View File

@@ -60,6 +60,104 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
.preview{ width: 40%; } .preview{ width: 40%; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
.input-typing:not(.no-animation) { position: relative; }
.input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:not(.no-animation):focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */
.form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
/* Floating labels para select */
.form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; }
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; }
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
.select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
.select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; }
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
.select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; }
/* ========== ESTILOS PARA CAMPO FOTO (SIN ANIMACIONES) ========== */
.no-animation, .no-border-style { transition: none !important; border: none; box-shadow: none !important; background: #fff !important; }
.no-animation:focus { transform: none !important; border: none; box-shadow: none !important; }
.no-animation:hover { animation: none !important; }
.no-animation::before, .no-animation::after { display: none !important; }
</style> </style>
</head> </head>
<body> <body>
@@ -69,36 +167,33 @@
<form id="formTransEdit" action="/IMPORTADORES/transportes/actualizar" method="POST" enctype="multipart/form-data" class="card p-4 shadow-sm card-hover position-relative h-auto"> <form id="formTransEdit" action="/IMPORTADORES/transportes/actualizar" method="POST" enctype="multipart/form-data" class="card p-4 shadow-sm card-hover position-relative h-auto">
<input type="hidden" name="id_transporte" value="<?= $t['id_transporte'] ?>"> <input type="hidden" name="id_transporte" value="<?= $t['id_transporte'] ?>">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6 form-group-animated">
<label class="form-label">Contenedor *</label> <label class="form-label">Contenedor</label>
<div class="form-floating-custom">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input name="vehiculo" id="vehiculo" class="form-control" maxlength="20" <input name="vehiculo" id="vehiculo" class="form-control input-pulse input-gradient" maxlength="20"
value="<?= htmlspecialchars($t['vehiculo']) ?>" required> value="<?= htmlspecialchars($t['vehiculo']) ?>" required>
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button> <label for="vehiculo" class="form-label">Contenedor</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;"></button>
</div> </div>
</div> </div>
<div class="col-md-6"> </div>
<label class="form-label">Identificación fiscal *</label>
<div class="col-md-6 form-group-animated">
<label class="form-label">Identificación fiscal</label>
<div class="form-floating-custom">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input name="identificador_fiscal" id="identificador_fiscal" class="form-control" maxlength="20" <input name="identificador_fiscal" id="identificador_fiscal" class="form-control input-pulse input-gradient" maxlength="20"
value="<?= htmlspecialchars($t['identificador_fiscal']) ?>" required> value="<?= htmlspecialchars($t['identificador_fiscal']) ?>" required>
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button> <label for="identificador_fiscal" class="form-label">Identificación fiscal</label>
<button type="button" class="btn btn-white ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;"></button>
</div> </div>
</div> </div>
<div class="col-md-6">
<label class="form-label">Foto actual</label><br>
<?php if($t['foto_url']): ?>
<img src="<?= $t['foto_url'] ?>" class="preview">
<?php else: ?>
<span class="text-muted">Sin foto</span>
<?php endif; ?>
</div> </div>
<div class="col-md-6">
<label class="form-label">Reemplazar foto (opcional)</label> <div class="col-md-6 form-group-animated">
<input type="file" name="foto" id="foto" class="form-control" accept="image/*"> <label class="form-label">Transportista</label>
</div> <div class="form-floating-custom">
<div class="col-md-6">
<label class="form-label">Transportista *</label>
<select name="id_transportista" id="transportista" class="form-select" required> <select name="id_transportista" id="transportista" class="form-select" required>
<?php foreach($transportistas as $tr): ?> <?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>" <option value="<?= $tr['id_transportista'] ?>"
@@ -107,16 +202,37 @@
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="transportista" class="form-label">Transportista</label>
</div> </div>
</div> </div>
<div class="text-end mt-4"> <div class="col-md-6"></div>
<div class="col-md-6 form-group-animated">
<label class="form-label">Foto actual</label><br>
<div class="form-floating-custom">
<?php if($t['foto_url']): ?>
<img src="<?= $t['foto_url'] ?>" class="preview">
<?php else: ?>
<span class="text-muted">Sin foto</span>
<?php endif; ?>
</div>
</div>
<div class="col-md-6 form-group-animated">
<label class="form-label">Reemplazar foto (opcional)</label>
<div class="form-floating-custom">
<input type="file" name="foto" id="foto" class="form-control no-animation no-border-style" accept="image/*">
</div>
</div>
</div>
<div class="text-end mt-4 form-group-animated">
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button> <button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button>
<a href="/IMPORTADORES/transportes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/transportes/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div> </div>
</form> </form>
</div> </div>
<!-- Modales movidos FUERA del formulario -->
<!-- Modal de ayuda - Contenedor --> <!-- Modal de ayuda - Contenedor -->
<div class="modal fade" id="infoContenedor" tabindex="-1" aria-labelledby="infoContenedorLabel" aria-hidden="true"> <div class="modal fade" id="infoContenedor" tabindex="-1" aria-labelledby="infoContenedorLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
@@ -176,11 +292,98 @@
return; return;
} }
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
return Swal.fire({ icon: 'error', title: 'Foto demasiado grande', text: 'La imagen no debe exceder 2 MB.' confirmButtonColor: '#dc3545' }); return Swal.fire({ icon: 'error', title: 'Foto demasiado grande', text: 'La imagen no debe exceder 2 MB.', confirmButtonColor: '#dc3545' });
} }
// Si todas las validaciones pasan, el formulario se envía. // Si todas las validaciones pasan, el formulario se envía.
this.submit(); this.submit();
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form-select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Validación del formulario
document.getElementById('formTransEdit').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const form = this;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
} else {
// Mostrar mensaje de error
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
}
});
});
// Manejo específico para selects
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('.form-select');
selects.forEach(select => {
// Manejar cambios en el select
select.addEventListener('change', function() {
updateSelectState(this);
});
// Estado inicial
updateSelectState(select);
});
function updateSelectState(select) {
// Actualizar el atributo value para que el CSS pueda detectarlo
select.setAttribute('value', select.value);
// Agregar/quitar clase valid basado en si hay valor seleccionado
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
// Solo agregar invalid si el campo es required y se ha intentado enviar
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
});
</script> </script>
</body> </body>

View File

@@ -67,15 +67,19 @@
<div class="content"> <div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🚛 Mis Transportes</h4> <h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🚛 Mis Transportes</h4>
<a href="/IMPORTADORES/transportes/crear" class="btn btn-success mb-3"> Nuevo Transporte</a> <a href="/IMPORTADORES/transportes/crear" class="btn btn-success mb-3 mt-auto w-auto btn-animated"> Nuevo Transporte</a>
<div class="card p-3 shadow-sm card-hover position-relative h-auto"> <div class="card p-3 shadow-sm card-hover position-relative h-auto">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped" id="tabla-transportes"> <table class="table table-striped" id="tabla-transportes">
<thead> <thead>
<tr> <tr>
<th>#</th><th>Contenedor</th><th>Ident. Fiscal</th> <th>#</th>
<th>Foto</th><th>Transportista</th> <th>Contenedor</th>
<th>Alta</th><th>Acciones</th> <th>Identifiación Fiscal</th>
<th>Foto</th>
<th>Transportista</th>
<th>Alta</th>
<th>Acciones</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -93,8 +97,8 @@
<td><?= $t['creado_en']->format('Y-m-d') ?></td> <td><?= $t['creado_en']->format('Y-m-d') ?></td>
<td> <td>
<a href="/IMPORTADORES/transportes/editar?id=<?= $t['id_transporte'] ?>" <a href="/IMPORTADORES/transportes/editar?id=<?= $t['id_transporte'] ?>"
class="btn btn-sm btn-primary">✏️</a> class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
<button class="btn btn-sm btn-danger" <button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
onclick="confirmDelete(<?= $t['id_transporte'] ?>)"> onclick="confirmDelete(<?= $t['id_transporte'] ?>)">
🗑️ 🗑️
</button> </button>
@@ -132,8 +136,14 @@
}); });
}); });
<?php if(isset($_GET['deleted'])): ?> <?php if (isset($_GET['created']) && $_GET['created']==='ok'): ?>
Swal.fire('¡Hecho!','Transporte eliminado.','success'); Swal.fire({ icon: 'success', title: 'Transporte creado', text: 'El transporte se creo correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['deleted']) && $_GET['deleted']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transporte eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['updated']) && $_GET['updated'] === 'ok'): ?>
Swal.fire({ icon: 'success', title: 'Transporte actualizado', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?> <?php endif; ?>
</script> </script>

View File

@@ -51,58 +51,50 @@
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; .btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
/* ========== ANIMACIONES PARA INPUTS ========== */ /* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
/* 1. Animación al hacer focus */ .form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; } .form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); } .form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 2. Animación al escribir */ .form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
.form-control:not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
/* 3. Efecto de shake en validación */
.form-control.shake { animation: shake 0.5s ease-in-out; }
@keyframes shake { @keyframes shake {
0%, 100% { transform: translateX(0); } 0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); } 25% { transform: translateX(-5px); }
75% { transform: translateX(5px); } 75% { transform: translateX(5px); }
} }
/* 4. Floating labels con animación */ /* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; } .form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; } .form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; } .form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label, .form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } .form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
/* 5. Efecto de pulso en hover */ .input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
.input-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse { @keyframes inputPulse {
0% { transform: scale(1); } 0% { transform: scale(1); }
50% { transform: scale(1.02); } 50% { transform: scale(1.02); }
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
/* 6. Gradiente animado en el borde */ .input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient { position: relative; overflow: hidden; } .input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
.input-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; } background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:focus::before { left: 100%; } .input-gradient:not(.no-animation):focus::before { left: 100%; }
/* 7. Efecto de brillo en validación exitosa */ .form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
.form-control.valid { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow { @keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } 0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); } 50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } 100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
} }
/* 8. Typing effect simulation */ .input-typing:not(.no-animation) { position: relative; }
.input-typing { position: relative; } .input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; } .input-typing:not(.no-animation):focus::after { opacity: 1; }
.input-typing:focus::after { opacity: 1; }
@keyframes blink { @keyframes blink {
0%, 50% { opacity: 1; } 0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; } 51%, 100% { opacity: 0; }
} }
/* 9. Efecto de ondas */ .input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple { position: relative; overflow: hidden; } .input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
.input-ripple::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(13, 110, 253, 0.2); background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; } .input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
.input-ripple:focus::before { width: 300px; height: 300px; } /* Animación para los campos del formulario */
/* 10. Slide-up animation para los campos */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; } .form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; } .form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; } .form-group-animated:nth-child(2) { animation-delay: 0.2s; }
@@ -112,49 +104,45 @@
.form-group-animated:nth-child(6) { animation-delay: 0.6s; } .form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; } .form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; } .form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } } @keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */ /* ========== ANIMACIONES PARA SELECT ========== */
/* 1. Estilos base para select */ .form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
.form-select {transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; } background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
/* 2. Animación al hacer focus */
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); .form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); } background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
/* 3. Animación cuando tiene valor seleccionado */ .form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
.form-select:not([value=""]):not(:invalid) { border-color: #198754; background-color: #f8fff9; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; } .form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
.form-floating-custom .form-select:focus ~ .form-label, /* Floating labels para select */
.form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } .form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
/* 5. Efecto de shake en validación para select */ transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; } .form-select.shake { animation: shake 0.5s ease-in-out; }
/* 6. Efecto de pulso en hover para select */
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; } .select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
/* 7. Gradiente animado para select */
.select-gradient { position: relative; overflow: hidden; } .select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; } .select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; } .select-gradient:focus::before { left: 100%; }
/* 8. Efecto de brillo en validación exitosa para select */
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; } .form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
/* 9. Efecto de rotación de la flecha al abrir */
.select-arrow-rotate { transition: all 0.3s ease; } .select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); } .select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
/* 10. Efecto de ondas para select */
.select-ripple { position: relative; overflow: hidden; } .select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; .select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; } background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; } .select-ripple:focus::after { width: 300px; height: 300px; }
/* 11. Efecto de scale suave */
.select-scale { transition: transform 0.2s ease; } .select-scale { transition: transform 0.2s ease; }
.select-scale:hover { transform: scale(1.02); } .select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); } .select-scale:focus { transform: scale(1.02) translateY(-2px); }
/* 12. Personalización de opciones (limitado en algunos navegadores) */
.form-select option { padding: 0.5rem; transition: all 0.2s ease; } .form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; } .form-select option:hover { background-color: #f8f9fa; }
/* 13. Indicador de estado */
.select-status { position: relative; } .select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%); .select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; } .select-status.valid::after { background-color: #198754; opacity: 1; } width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; } .select-status:invalid::after { background-color: #dc3545; opacity: 1; }
</style> </style>
</head> </head>
@@ -222,7 +210,7 @@
<div class="col-md-4 form-group-animated"> <div class="col-md-4 form-group-animated">
<div class="form-floating-custom"> <div class="form-floating-custom">
<select id="pais" name="pais" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required> <select id="pais" name="pais" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value=""></option> <option value="">-- Selecciona un País --</option>
<?php foreach($paises as $p): ?> <?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>"><?= htmlspecialchars($p['nombre']) ?></option> <option value="<?= $p['id_pais'] ?>"><?= htmlspecialchars($p['nombre']) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
@@ -265,15 +253,61 @@
</div> </div>
<script> <script>
document.getElementById('formAltaTransportista').addEventListener('submit', function(e) { document.addEventListener('DOMContentLoaded', function() {
e.preventDefault(); // Referencias a elementos del DOM
const rfcInput = document.getElementById('rfc'); const form = document.getElementById('formAltaTransportista');
const curpInput = document.getElementById('curp'); const inputs = document.querySelectorAll('input.form-control, select.form-select');
const telefonoInput = document.getElementById('telefono'); const selects = document.querySelectorAll('.form-select');
const rfc = rfcInput.value.trim().toUpperCase(); // Configurar validación en tiempo real para inputs
const curp = curpInput.value.trim(); inputs.forEach(input => {
const telefono = telefonoInput.value.trim(); input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Configurar selects
selects.forEach(select => {
select.addEventListener('change', function() {
updateSelectState(this);
});
updateSelectState(select);
});
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
// Event listener para el formulario
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
const rfc = document.getElementById('rfc').value.trim().toUpperCase();
const curp = document.getElementById('curp').value.trim();
const telefono = document.getElementById('telefono').value.trim();
const clave = document.getElementById('clave').value.trim(); const clave = document.getElementById('clave').value.trim();
const nombre = document.getElementById('nombre').value.trim(); const nombre = document.getElementById('nombre').value.trim();
const caat = document.getElementById('caat').value.trim(); const caat = document.getElementById('caat').value.trim();
@@ -322,7 +356,7 @@
return; return;
} }
if (!telefonoRegex.test(telefono)) { if (!telefonoRegex.test(telefono)) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos (ej. 123 4567890).', onfirmButtonColor:'#dc3545'}); Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos (ej. 123 4567890).', confirmButtonColor:'#dc3545'});
document.getElementById('telefono').focus(); document.getElementById('telefono').focus();
return; return;
} }
@@ -356,99 +390,17 @@
document.getElementById('domicilio').focus(); document.getElementById('domicilio').focus();
return; return;
} }
document.getElementById('rfc').value = rfc;
document.getElementById('curp').value = curp; // Actualizar valores procesados
const rfcElement = document.getElementById('rfc');
const curpElement = document.getElementById('curp');
if (rfcElement) rfcElement.value = rfc;
if (curpElement) curpElement.value = curp;
// Si todas las validaciones son correctas: Enviar el formulario
this.submit(); this.submit();
});
document.getElementById('pais').addEventListener('change', e => {
const pid = e.target.value;
const estadoSelect = document.getElementById('entidad');
const ciudadSelect = document.getElementById('ciudad');
estadoSelect.innerHTML = '<option value="">Cargando…</option>';
estadoSelect.disabled = true;
ciudadSelect.innerHTML = '<option value="">Primero estado…</option>';
ciudadSelect.disabled = true;
if (!pid) { estadoSelect.innerHTML = '<option value="">Primero país…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/estados?pais=${pid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar estados'); return r.json(); })
.then(list => {
estadoSelect.innerHTML = '<option value=""></option>';
list.forEach(st => estadoSelect.add(new Option(st.nombre, st.id_estado)));
estadoSelect.disabled = false;
})
.catch(() => {
estadoSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error',title:'Error',text:'No se pudieron cargar los estados',confirmButtonColor:'#dc3545'});
});
});
document.getElementById('entidad').addEventListener('change', e => {
const eid = e.target.value;
const ciudadSelect = document.getElementById('ciudad');
ciudadSelect.innerHTML = '<option value="">Cargando…</option>';
ciudadSelect.disabled = true;
if (!eid) { ciudadSelect.innerHTML = '<option value="">Primero estado…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/ciudades?estado=${eid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar ciudades'); return r.json(); })
.then(list => {
ciudadSelect.innerHTML = '<option value=""></option>';
list.forEach(ct => ciudadSelect.add(new Option(ct.nombre, ct.id_ciudad)));
ciudadSelect.disabled = false;
})
.catch(() => {
ciudadSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error',title:'Error',text:'No se pudieron cargar las ciudades',confirmButtonColor:'#dc3545'});
});
});
// Switch Persona Moral
document.getElementById('personaMoralSwitch').addEventListener('change', function() {
const checked = this.checked;
const label = document.getElementById('labelNombre');
const curp = document.getElementById('curp');
const hidden = document.getElementById('es_persona_moral');
if (checked) {
label.textContent = 'Razón Social *';
curp.value = '';
curp.disabled = true;
curp.removeAttribute('required');
hidden.value = '1';
} else {
label.textContent = 'Nombre *';
curp.disabled = false;
curp.setAttribute('required', 'required');
hidden.value = '0';
}
});
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form_select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Validación del formulario
document.getElementById('formAltaAgencias').addEventListener('submit', function(e) {
e.preventDefault();
// Validar campos
let isValid = true; let isValid = true;
inputs.forEach(input => { inputs.forEach(input => {
if (!input.checkValidity()) { if (!input.checkValidity()) {
@@ -456,16 +408,76 @@
isValid = false; isValid = false;
} }
}); });
})
}
if (isValid) { document.getElementById('pais').addEventListener('change', e => {
// Aquí puedes agregar tu lógica de envío const pid = e.target.value;
Swal.fire({ const estadoSelect = document.getElementById('entidad');
icon: 'success', const ciudadSelect = document.getElementById('ciudad');
title: '¡Éxito!',
text: 'Agencia registrada correctamente', estadoSelect.innerHTML = '<option value="">Cargando…</option>';
showConfirmButton: false, estadoSelect.disabled = true;
timer: 1500 ciudadSelect.innerHTML = '<option value="">Primero estado…</option>';
ciudadSelect.disabled = true;
if (!pid) { estadoSelect.innerHTML = '<option value="">Primero país…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/estados?pais=${pid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar estados'); return r.json(); })
.then(list => {
estadoSelect.innerHTML = '<option value="">-- Selecciona un Estado --</option>';
list.forEach(st => estadoSelect.add(new Option(st.nombre, st.id_estado)));
estadoSelect.disabled = false;
})
.catch(() => {
estadoSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error', title:'Error', text:'No se pudieron cargar los estados', confirmButtonColor:'#dc3545'});
}); });
});
document.getElementById('entidad').addEventListener('change', e => {
const eid = e.target.value;
const ciudadSelect = document.getElementById('ciudad');
ciudadSelect.innerHTML = '<option value="">Cargando…</option>';
ciudadSelect.disabled = true;
if (!eid) { ciudadSelect.innerHTML = '<option value="">Primero estado…</option>'; return; }
fetch(`/IMPORTADORES/transportistas/ciudades?estado=${eid}`)
.then(r => { if (!r.ok) throw new Error('Error al cargar ciudades'); return r.json(); })
.then(list => {
ciudadSelect.innerHTML = '<option value="">-- Seleciona una ciudad --</option>';
list.forEach(ct => ciudadSelect.add(new Option(ct.nombre, ct.id_ciudad)));
ciudadSelect.disabled = false;
})
.catch(() => {
ciudadSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ icon:'error', title:'Error', text:'No se pudieron cargar las ciudades', confirmButtonColor:'#dc3545'});
});
});
// Switch Persona Moral
document.getElementById('personaMoralSwitch')?.addEventListener('change', function() {
const checked = this.checked;
const label = document.getElementById('labelNombre');
const curp = document.getElementById('curp');
const hidden = document.getElementById('es_persona_moral');
if (label) {
label.textContent = checked ? 'Razón Social *' : 'Nombre *';
}
if (curp && hidden) {
if (checked) {
curp.value = '';
curp.disabled = true;
curp.removeAttribute('required');
hidden.value = '1';
} else {
curp.disabled = false;
curp.setAttribute('required', 'required');
hidden.value = '0';
}
} }
}); });
}); });

View File

@@ -58,6 +58,99 @@
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; .btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; } .btn-animated:hover::before { left: 100%; }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation):focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px); }
.form-control:not(.no-animation):not(:placeholder-shown) { border-color: #198754; background-color: #f8fff9; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
/* Floating labels para inputs texto */
.form-floating-custom { position: relative; margin-bottom: 1.5rem; }
.form-floating-custom .form-control { padding: 1rem 0.75rem 0.5rem 0.75rem; height: auto; }
.form-floating-custom .form-label { position: absolute; top: 0.75rem; left: 0.75rem; transition: all 0.3s ease; pointer-events: none; color: #6c757d; z-index: 2; }
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; }
.input-pulse:hover:not(.no-animation) { animation: inputPulse 0.6s ease-in-out; }
@keyframes inputPulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.input-gradient:not(.no-animation) { position: relative; overflow: hidden; }
.input-gradient:not(.no-animation)::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.input-gradient:not(.no-animation):focus::before { left: 100%; }
.form-control.valid:not(.no-animation) { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; }
@keyframes successGlow {
0% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
50% { box-shadow: 0 0 20px rgba(25, 135, 84, 0.8); }
100% { box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); }
}
.input-typing:not(.no-animation) { position: relative; }
.input-typing:not(.no-animation)::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; }
.input-typing:not(.no-animation):focus::after { opacity: 1; }
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.input-ripple:not(.no-animation) { position: relative; overflow: hidden; }
.input-ripple:not(.no-animation)::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.input-ripple:not(.no-animation):focus::before { width: 300px; height: 300px; }
/* Animación para los campos del formulario */
.form-group-animated { opacity: 0; transform: translateY(20px); animation: slideUp 0.6s ease-out forwards; }
.form-group-animated:nth-child(1) { animation-delay: 0.1s; }
.form-group-animated:nth-child(2) { animation-delay: 0.2s; }
.form-group-animated:nth-child(3) { animation-delay: 0.3s; }
.form-group-animated:nth-child(4) { animation-delay: 0.4s; }
.form-group-animated:nth-child(5) { animation-delay: 0.5s; }
.form-group-animated:nth-child(6) { animation-delay: 0.6s; }
.form-group-animated:nth-child(7) { animation-delay: 0.7s; }
.form-group-animated:nth-child(8) { animation-delay: 0.8s; }
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* ========== ANIMACIONES PARA SELECT ========== */
.form-select { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; cursor: pointer;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; }
.form-select:focus { border-color: #0d6efd; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25), 0 0 20px rgba(13, 110, 253, 0.3); transform: translateY(-2px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select:not([value=""]):valid { border-color: #198754; background-color: #f8fff9;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23198754' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 6 7 7 7-7'/%3e%3c/svg%3e"); }
.form-select[value=""] { border-color: #dee2e6; background-color: #fff; }
/* Floating labels para select */
.form-floating-custom .form-select ~ .form-label { position: absolute; top: 50%; left: 0.75rem; transform: translateY(-50%); pointer-events: none;
transition: all 0.3s ease; color: #6c757d; font-size: 0.875rem; background: white; padding: 0 0.25rem; z-index: 1; opacity: 0; }
.form-floating-custom .form-select:focus ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-floating-custom .form-select:not([value=""]):valid ~ .form-label { opacity: 1; top: 0; transform: translateY(-50%) scale(0.85); color: #0d6efd; font-weight: 600; }
.form-select.shake { animation: shake 0.5s ease-in-out; }
.select-pulse:hover { animation: inputPulse 0.6s ease-in-out; }
.select-gradient { position: relative; overflow: hidden; }
.select-gradient::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.3), transparent); transition: left 0.5s; pointer-events: none; z-index: 1; }
.select-gradient:focus::before { left: 100%; }
.form-select.valid { border-color: #198754; background-color: #f8fff9; animation: successGlow 1s ease-in-out; }
.select-arrow-rotate { transition: all 0.3s ease; }
.select-arrow-rotate:focus { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230d6efd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 10 7-7 7 7'/%3e%3c/svg%3e"); }
.select-ripple { position: relative; overflow: hidden; }
.select-ripple::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0;
background: rgba(13, 110, 253, 0.2); border-radius: 50%; transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; pointer-events: none; }
.select-ripple:focus::after { width: 300px; height: 300px; }
.select-scale { transition: transform 0.2s ease; }
.select-scale:hover {transform: scale(1.02); }
.select-scale:focus { transform: scale(1.02) translateY(-2px); }
.form-select option { padding: 0.5rem; transition: all 0.2s ease; }
.form-select option:hover { background-color: #f8f9fa; }
.select-status { position: relative; }
.select-status::after { content: ''; position: absolute; right: 2.5rem; top: 50%; transform: translateY(-50%);
width: 8px; height: 8px; border-radius: 50%; background-color: #dc3545; opacity: 0; transition: opacity 0.3s ease; }
.select-status.valid::after { background-color: #198754; opacity: 1; }
.select-status:invalid::after { background-color: #dc3545; opacity: 1; }
</style> </style>
</head> </head>
<body> <body>
@@ -67,41 +160,58 @@
<form action="/IMPORTADORES/transportistas/actualizar" method="POST" class="card p-4 shadow-sm card-hover position-relative h-auto" id="formEditTransportista"> <form action="/IMPORTADORES/transportistas/actualizar" method="POST" class="card p-4 shadow-sm card-hover position-relative h-auto" id="formEditTransportista">
<input type="hidden" name="id_transportista" value="<?= $t['id_transportista'] ?>"> <input type="hidden" name="id_transportista" value="<?= $t['id_transportista'] ?>">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label class="form-label">Clave</label> <div class="form-floating-custom">
<input name="clave" value="<?= htmlspecialchars($t['clave_identificador']) ?>" <input name="clave" value="<?= htmlspecialchars($t['clave_identificador']) ?>"
id="clave" class="form-control" maxlength="8" required> id="clave" class="form-control input-pulse input-gradient" maxlength="8" required>
<label for="clave" class="form-label">Clave</label>
</div> </div>
<div class="col-md-4">
<label class="form-label">Nombre</label>
<input name="nombre" value="<?= htmlspecialchars($t['nombre']) ?>"
id="nombre" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label">RFC</label>
<input name="rfc" value="<?= htmlspecialchars($t['rfc']) ?>"
id="rfc" class="form-control" maxlength="13" required>
</div>
<div class="col-md-4">
<label class="form-label">CURP</label>
<input name="curp" value="<?= htmlspecialchars($t['curp']) ?>"
id="curp" class="form-control" maxlength="18">
</div>
<div class="col-md-4">
<label class="form-label">Teléfono</label>
<input name="telefono" value="<?= htmlspecialchars($t['telefono']) ?>"
id="telefono" class="form-control" maxlength="11" required>
</div>
<div class="col-md-4">
<label class="form-label">Código caat</label>
<input name="caat" value="<?= htmlspecialchars($t['caat']) ?>"
id="caat" class="form-control" maxlength="20" required>
</div> </div>
<div class="col-md-4"> <div class="col-md-4 form-group-animated">
<label class="form-label">País</label> <div class="form-floating-custom">
<select id="pais" name="pais" class="form-select" required> <input name="nombre" value="<?= htmlspecialchars($t['nombre']) ?>"
<option value="">Selecciona país</option> id="nombre" class="form-control input-pulse input-gradient" required>
<label for="nombre" class="form-label">Nombre</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="rfc" value="<?= htmlspecialchars($t['rfc']) ?>"
id="rfc" class="form-control input-pulse input-gradient" maxlength="13" required>
<label for="rfc" class="form-label">RFC</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="curp" value="<?= htmlspecialchars($t['curp']) ?>"
id="curp" class="form-control input-pulse input-gradient" maxlength="18">
<label for="curp" class="form-label">CURP</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="telefono" value="<?= htmlspecialchars($t['telefono']) ?>"
id="telefono" class="form-control input-pulse input-gradient" maxlength="11" required>
<label for="telefono" class="form-label">Teléfono</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<input name="caat" value="<?= htmlspecialchars($t['caat']) ?>"
id="caat" class="form-control input-pulse input-gradient" maxlength="20" required>
<label for="caat" class="form-label">Código caat</label>
</div>
</div>
<div class="col-md-4 form-group-animated">
<div class="form-floating-custom">
<select id="pais" name="pais" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un País --</option>
<?php foreach($paises as $p): ?> <?php foreach($paises as $p): ?>
<option value="<?= $p['id_pais'] ?>" <option value="<?= $p['id_pais'] ?>"
<?= $p['id_pais']==$t['pais']?'selected':'' ?>> <?= $p['id_pais']==$t['pais']?'selected':'' ?>>
@@ -109,11 +219,14 @@
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="pais" class="form-label">País</label>
</div> </div>
<div class="col-md-4"> </div>
<label class="form-label">Estado</label>
<select id="entidad" name="entidad" class="form-select" required> <div class="col-md-4 form-group-animated">
<option value="">Selecciona estado</option> <div class="form-floating-custom">
<select id="entidad" name="entidad" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona un Estado --</option>
<?php foreach($estados as $e): ?> <?php foreach($estados as $e): ?>
<option value="<?= $e['id_estado'] ?>" <option value="<?= $e['id_estado'] ?>"
<?= $e['id_estado']==$t['entidad_federativa']?'selected':'' ?>> <?= $e['id_estado']==$t['entidad_federativa']?'selected':'' ?>>
@@ -121,11 +234,14 @@
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="entidad" class="form-label">Entidad</label>
</div> </div>
<div class="col-md-4"> </div>
<label class="form-label">Ciudad</label>
<select id="ciudad" name="ciudad" class="form-select" required> <div class="col-md-4 form-group-animated">
<option value="">Selecciona ciudad</option> <div class="form-floating-custom">
<select id="ciudad" name="ciudad" class="form-select select-pulse select-gradient select-arrow-rotate select-status" required>
<option value="">-- Selecciona una Ciudad --</option>
<?php foreach($ciudades as $c): ?> <?php foreach($ciudades as $c): ?>
<option value="<?= $c['id_ciudad'] ?>" <option value="<?= $c['id_ciudad'] ?>"
<?= $c['id_ciudad']==$t['ciudad']?'selected':'' ?>> <?= $c['id_ciudad']==$t['ciudad']?'selected':'' ?>>
@@ -133,14 +249,19 @@
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="ciudad" class="form-label">Ciudad</label>
</div>
</div> </div>
<div class="col-md-12"> <div class="col-md-12 form-group-animated">
<label class="form-label">Domicilio Completo</label> <div class="form-floating-custom">
<textarea name="domicilio" id="domicilio" class="form-control" rows="3" required><?= htmlspecialchars($t['domicilio']) ?></textarea> <textarea name="domicilio" id="domicilio" class="form-control input-pulse input-gradient" rows="1" required><?= htmlspecialchars($t['domicilio']) ?></textarea>
<label for="domicilio" class="form-label">Domicilio Completo</label>
</div> </div>
</div> </div>
<div class="text-end mt-4"> </div>
<div class="text-end mt-4 form-group-animated">
<button class="btn btn-success px-4 mt-auto w-auto btn-animated">💾 Guardar Cambios</button> <button class="btn btn-success px-4 mt-auto w-auto btn-animated">💾 Guardar Cambios</button>
<a href="/IMPORTADORES/transportistas/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a> <a href="/IMPORTADORES/transportistas/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
</div> </div>
@@ -151,13 +272,9 @@
document.getElementById('formEditTransportista').addEventListener('submit', function(e) { document.getElementById('formEditTransportista').addEventListener('submit', function(e) {
e.preventDefault(); // Prevenir envío por defecto e.preventDefault(); // Prevenir envío por defecto
const rfcInput = document.getElementById('rfc'); const rfc = document.getElementById('rfc').value.trim().toUpperCase();
const curpInput = document.getElementById('curp'); const curp = document.getElementById('curp').value.trim();
const telefonoInput = document.getElementById('telefono'); const telefono = document.getElementById('telefono').value.trim();
const rfc = rfcInput.value.trim().toUpperCase();
const curp = curpInput.value.trim();
const telefono = telefonoInput.value.trim();
const clave = document.getElementById('clave').value.trim(); const clave = document.getElementById('clave').value.trim();
const nombre = document.getElementById('nombre').value.trim(); const nombre = document.getElementById('nombre').value.trim();
const caat = document.getElementById('caat').value.trim(); const caat = document.getElementById('caat').value.trim();
@@ -248,8 +365,16 @@
document.getElementById('rfc').value = rfc; document.getElementById('rfc').value = rfc;
document.getElementById('curp').value = curp; document.getElementById('curp').value = curp;
// Enviar el formulario // Si todas las validaciones son correctas: Enviar el formulario
this.submit(); this.submit();
// Validar campos
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
}); });
// Funcionalidad de selects en cascada // Funcionalidad de selects en cascada
@@ -277,7 +402,7 @@
return r.json(); return r.json();
}) })
.then(list => { .then(list => {
estadoSelect.innerHTML = '<option value="">Selecciona estado</option>'; estadoSelect.innerHTML = '<option value="">-- Selecciona un Estado --</option>';
list.forEach(st => { list.forEach(st => {
const option = new Option(st.nombre, st.id_estado); const option = new Option(st.nombre, st.id_estado);
estadoSelect.add(option); estadoSelect.add(option);
@@ -287,12 +412,7 @@
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
estadoSelect.innerHTML = '<option value="">Error al cargar</option>'; estadoSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ Swal.fire({ icon: 'error', title: 'Error', text: 'No se pudieron cargar los estados', confirmButtonColor: '#dc3545' });
icon: 'error',
title: 'Error',
text: 'No se pudieron cargar los estados',
confirmButtonColor: '#dc3545'
});
}); });
}); });
@@ -316,7 +436,7 @@
return r.json(); return r.json();
}) })
.then(list => { .then(list => {
ciudadSelect.innerHTML = '<option value="">Selecciona ciudad</option>'; ciudadSelect.innerHTML = '<option value="">-- Selecciona una Ciudad --</option>';
list.forEach(ct => { list.forEach(ct => {
const option = new Option(ct.nombre, ct.id_ciudad); const option = new Option(ct.nombre, ct.id_ciudad);
ciudadSelect.add(option); ciudadSelect.add(option);
@@ -326,13 +446,186 @@
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
ciudadSelect.innerHTML = '<option value="">Error al cargar</option>'; ciudadSelect.innerHTML = '<option value="">Error al cargar</option>';
Swal.fire({ Swal.fire({ icon: 'error', title: 'Error', text: 'No se pudieron cargar las ciudades', confirmButtonColor: '#dc3545' });
icon: 'error',
title: 'Error',
text: 'No se pudieron cargar las ciudades',
confirmButtonColor: '#dc3545'
}); });
}); });
// Script para manejar las animaciones de validación
document.addEventListener('DOMContentLoaded', function() {
const inputs = document.querySelectorAll('input.form-control, select.form-select');
inputs.forEach(input => {
// Validación en tiempo real
input.addEventListener('input', function() {
if (this.checkValidity()) {
this.classList.remove('shake');
this.classList.add('valid');
} else {
this.classList.remove('valid');
}
});
// Efecto shake en campos inválidos
input.addEventListener('invalid', function() {
this.classList.add('shake');
setTimeout(() => {
this.classList.remove('shake');
}, 500);
});
});
// Validación del formulario
document.getElementById('formEditTransportista').addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
const form = this;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.classList.add('shake');
isValid = false;
}
});
// Si todo es válido, enviar el formulario
if (isValid) {
// Mostrar indicador de carga
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
// Aquí puedes enviar el formulario real
form.submit();
}
});
});
// Manejo específico para selects
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('.form-select');
selects.forEach(select => {
// Manejar cambios en el select
select.addEventListener('change', function() {
updateSelectState(this);
});
// Estado inicial
updateSelectState(select);
});
function updateSelectState(select) {
// Actualizar el atributo value para que el CSS pueda detectarlo
select.setAttribute('value', select.value);
// Agregar/quitar clase valid basado en si hay valor seleccionado
if (select.value && select.value !== '') {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
// Solo agregar invalid si el campo es required y se ha intentado enviar
if (select.hasAttribute('required') && select.closest('form')?.classList.contains('was-validated')) {
select.classList.add('invalid');
}
}
}
});
document.addEventListener('DOMContentLoaded', function () {
const paisSelect = document.getElementById('pais');
const estadoSelect = document.getElementById('entidad');
const ciudadSelect = document.getElementById('ciudad');
if (!paisSelect || !estadoSelect || !ciudadSelect) return;
// ✅ Remueve clases de animación/validación
function cleanVisualState(select) {
select.classList.remove('valid', 'invalid', 'shake', 'loading');
}
// ✅ Reset de select con mensaje personalizado
function resetSelect(select, message, disabled = true) {
if (!select) return;
select.innerHTML = `<option value="">${message}</option>`;
select.disabled = disabled;
cleanVisualState(select);
updateSelectState(select);
}
// ✅ Actualiza el estado visual según el valor
function updateSelectState(select) {
select.setAttribute('value', select.value);
if (select.value) {
select.classList.add('valid');
select.classList.remove('invalid');
} else {
select.classList.remove('valid');
}
}
// ✅ Listener para cambiar país
paisSelect.addEventListener('change', function () {
const pid = this.value;
resetSelect(estadoSelect, pid ? 'Cargando estados…' : 'Primero selecciona un país…');
resetSelect(ciudadSelect, 'Primero selecciona un estado…');
if (!pid) return;
// Lógica real de carga
fetch(`/IMPORTADORES/transportistas/estados?pais=${pid}`)
.then(r => r.ok ? r.json() : Promise.reject('Error al cargar'))
.then(data => {
estadoSelect.innerHTML = '<option value="">-- Selecciona un Estado --</option>';
data.forEach(e => {
const o = new Option(e.nombre, e.id_estado);
estadoSelect.add(o);
});
estadoSelect.disabled = false;
updateSelectState(estadoSelect);
})
.catch(err => {
console.error(err);
resetSelect(estadoSelect, 'Error al cargar estados');
});
});
// ✅ Listener para cambiar estado
estadoSelect.addEventListener('change', function () {
const eid = this.value;
resetSelect(ciudadSelect, eid ? 'Cargando ciudades…' : 'Primero selecciona un estado…');
if (!eid) return;
fetch(`/IMPORTADORES/transportistas/ciudades?estado=${eid}`)
.then(r => r.ok ? r.json() : Promise.reject('Error al cargar'))
.then(data => {
ciudadSelect.innerHTML = '<option value="">-- Selecciona una Ciudad --</option>';
data.forEach(c => {
const o = new Option(c.nombre, c.id_ciudad);
ciudadSelect.add(o);
});
ciudadSelect.disabled = false;
updateSelectState(ciudadSelect);
})
.catch(err => {
console.error(err);
resetSelect(ciudadSelect, 'Error al cargar ciudades');
});
});
// ✅ Actualizar ciudad visual
ciudadSelect.addEventListener('change', function () {
updateSelectState(this);
});
// ✅ Aplicar estado visual inicial
[paisSelect, estadoSelect, ciudadSelect].forEach(select => {
updateSelectState(select);
});
}); });
</script> </script>

View File

@@ -64,8 +64,8 @@
<div class="content"> <div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🚚 Mis Transportistas</h4> <h4 class="mb-4 animate__animated animate__fadeInDown title_glow">🚚 Mis Transportistas</h4>
<a href="/IMPORTADORES/transportistas/alta" class="btn btn-success mb-3 card-hover position-relative h-auto mt-auto w-auto btn-animated"> Nuevo Transportista</a> <a href="/IMPORTADORES/transportistas/alta" class="btn btn-success mb-3 mt-auto w-auto btn-animated"> Nuevo Transportista</a>
<div class="card p-3 shadow-sm"> <div class="card p-3 shadow-sm card-hover position-relative h-auto">
<div class="table-responsive"> <div class="table-responsive">
<table id="transportistas-table" class="display nowrap" style="width:100%"> <table id="transportistas-table" class="display nowrap" style="width:100%">
<thead> <thead>
@@ -145,6 +145,9 @@
}); });
} }
<?php if (isset($_GET['created']) && $_GET['created']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transportista creado', text: 'El transportista se creo correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
// Mostrar alert de “borrado” al volver de la acción // Mostrar alert de “borrado” al volver de la acción
<?php if (isset($_GET['deleted']) && $_GET['deleted']==='ok'): ?> <?php if (isset($_GET['deleted']) && $_GET['deleted']==='ok'): ?>
Swal.fire({ icon: 'success', title: 'Transportista eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' }); Swal.fire({ icon: 'success', title: 'Transportista eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' });