From 0e88fea37e1da0d0d2acf7ab1ee7b7e1139ef891 Mon Sep 17 00:00:00 2001 From: Alexeer Date: Mon, 14 Jul 2025 13:27:15 -0600 Subject: [PATCH] =?UTF-8?q?Aprobaci=C3=B3n=20de=20Agencias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/administrador.php | 45 +- app/controllers/agencias.php | 95 ++++- app/controllers/patente.php | 57 ++- app/controllers/proveedores.php | 133 +++--- app/controllers/transportes.php | 2 +- app/controllers/transportistas.php | 4 +- views/admin/agencias_activas.php | 10 + views/admin/alta_usuarios.php | 157 ++++--- views/admin/aprobar_agencias.php | 24 ++ views/agencias/alta_agentes.php | 235 ++++++++++- views/choferes/crear.php | 99 ++--- views/choferes/editar.php | 268 +++++++++--- views/locaciones/alta_locaciones.php | 195 +++++---- views/locaciones/gestion_locaciones.php | 2 +- views/partials/sidebar_administrador.php | 2 +- views/partials/sidebar_agencia.php | 2 +- views/partials/sidebar_agente.php | 2 +- views/partials/sidebar_configuracion.php | 2 +- views/partials/sidebar_importador.php | 2 +- views/patente/alta_patente.php | 160 +++++-- views/patente/editar.php | 125 ++++-- views/patente/lista.php | 10 +- views/productos_frecuentes/alta.php | 209 +++++++--- views/productos_frecuentes/lista.php | 8 +- views/proveedores/crear.php | 109 ++++- views/reset/change_password.php | 8 +- views/reset/verificar_codigo.php | 8 +- views/solicitud_importacion/crear.php | 173 +++++++- views/solicitud_importacion/editar.php | 180 +++++++- views/solicitud_importacion/lista.php | 36 +- views/transportes/crear.php | 230 +++++++---- views/transportes/editar.php | 279 +++++++++++-- views/transportes/lista.php | 66 +-- views/transportistas/alta.php | 506 ++++++++++++----------- views/transportistas/editar.php | 485 +++++++++++++++++----- views/transportistas/lista.php | 7 +- 36 files changed, 2807 insertions(+), 1128 deletions(-) diff --git a/app/controllers/administrador.php b/app/controllers/administrador.php index 7f949aa..98e260a 100644 --- a/app/controllers/administrador.php +++ b/app/controllers/administrador.php @@ -463,13 +463,14 @@ function aprobarAgencias() function aprobar_agencia() { - $conn = getConnection(); + $conn = getConnection(); $id = $_GET['id'] ?? null; $usuario_id = $_SESSION['usuario_id']; if (!$id || !is_numeric($id)) { - die("❌ ID inválido."); + header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=invalid_id"); + exit; } // Obtener la solicitud @@ -477,8 +478,9 @@ function aprobar_agencia() $stmt = sqlsrv_query($conn, $sql, [$id]); $solicitud = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); - if (!$solicitud) { - die("❌ Solicitud no encontrada."); + if (!$solicitud) { + header("Location: /IMPORTADORES/administrador/aprobarAgencias?error=invalid_request"); + exit; } // Validar que no haya sido aprobada ya @@ -511,6 +513,7 @@ function aprobar_agencia() // Insertar en agencias_aduanales $sqlInsert = "INSERT INTO agencias_aduanales (nombre_agencia, rfc_agencia, direccion, telefono, email, creado_en, creado_por) + OUTPUT INSERTED.id_agencia VALUES (?, ?, ?, ?, ?, GETDATE(), ?) "; $stmtInsert = sqlsrv_query($conn, $sqlInsert, [ @@ -518,37 +521,38 @@ function aprobar_agencia() ]); 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 - $sqlGetAgenciaId = "SELECT SCOPE_IDENTITY() AS id_agencia"; - $stmtGetAgenciaId = sqlsrv_query($conn, $sqlGetAgenciaId); - $agenciaIdRow = sqlsrv_fetch_array($stmtGetAgenciaId, SQLSRV_FETCH_ASSOC); - $id_agencia = $agenciaIdRow['id_agencia'] ?? null; + $agenciaIdRow = sqlsrv_fetch_array($stmtInsert, SQLSRV_FETCH_ASSOC); + $id_agencia = $agenciaIdRow['id_agencia'] ?? null; 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 $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, ?) "; $stmtInsert2 = sqlsrv_query($conn, $sqlInsert2, [$admin_name_encrypt, $admin_email_encrypt, $password_hash, $tipo, $usuario_id]); 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 - $sqlGetId = "SELECT SCOPE_IDENTITY() AS id_usuario"; - $stmtGetId = sqlsrv_query($conn, $sqlGetId); - $idUsuarioRow = sqlsrv_fetch_array($stmtGetId, SQLSRV_FETCH_ASSOC); + $idUsuarioRow = sqlsrv_fetch_array($stmtInsert2, SQLSRV_FETCH_ASSOC); $id_usuario = $idUsuarioRow['id_usuario'] ?? null; 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 @@ -556,7 +560,8 @@ function aprobar_agencia() $stmtUpdateAgencia = sqlsrv_query($conn, $sqlUpdateAgencia, [$id_usuario, $id_agencia]); 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 @@ -564,7 +569,8 @@ function aprobar_agencia() $stmtInfo = sqlsrv_query($conn, $sqlInfo, [$id_usuario, $admin_name, $admin_email]); 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 @@ -577,7 +583,8 @@ function aprobar_agencia() $stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$usuario_id, $id]); 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 @@ -624,7 +631,7 @@ function aprobar_agencia() // Registrar en bitácora registrar_bitacora_agencia($id_agencia, $nombre, 'CREACION', $usuario_id); - header("Location: /IMPORTADORES/administrador/agenciasActivas"); + header("Location: /IMPORTADORES/administrador/agenciasActivas?success=created"); exit; } diff --git a/app/controllers/agencias.php b/app/controllers/agencias.php index e22b456..6685c78 100644 --- a/app/controllers/agencias.php +++ b/app/controllers/agencias.php @@ -72,8 +72,6 @@ function alta() FROM agente_agencia aa INNER JOIN usuarios_sistema u ON aa.id_agente = u.id_usuario - INNER JOIN agencias_aduanales a - ON aa.id_agente = a.id_administrador LEFT JOIN usuarios_sistema creador ON u.creado_por = creador.id_usuario WHERE aa.id_agencia = ? @@ -108,6 +106,12 @@ function guardarAgente() $conn = getConnection(); 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)) { error_log("ERROR: No se pudo iniciar la transacción"); throw new Exception("Error al iniciar transacción"); @@ -121,13 +125,25 @@ function guardarAgente() $password = $_POST['password'] ?? ''; $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)) { + 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"); exit; } 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"); exit; } @@ -137,15 +153,27 @@ function guardarAgente() $email_encrypted = encrypt($email); $password_hash = password_hash($password, PASSWORD_DEFAULT); + error_log("Datos encriptados exitosamente"); + // 3. Validar duplicado por email encriptado $sqlCheck = "SELECT COUNT(*) AS total FROM usuarios_sistema WHERE email = ?"; $stmtCheck = sqlsrv_query($conn, $sqlCheck, [$email_encrypted]); - $rowCheck = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC); + + 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); if ($rowCheck['total'] > 0) { + error_log("ERROR: Email ya existe"); header("Location: /IMPORTADORES/agencias/alta?error=email_exists"); exit; } + error_log("Validación de duplicado pasada"); + // 4. Insertar usuario $sqlInsert = "INSERT INTO usuarios_sistema (nombre, email, password_hash, tipo_usuario, activo, creado_en, dos_factores, creado_por) @@ -153,10 +181,20 @@ function guardarAgente() VALUES (?, ?, ?, ?, 1, GETDATE(), 0, ?) "; $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); if ($stmtInsert === false) { error_log("ERROR: Fallo al insertar usuario"); + error_log("SQL: $sqlInsert"); + error_log("Errores SQL Server:"); error_log(print_r(sqlsrv_errors(), true)); throw new Exception("Error al insertar usuario"); } @@ -166,41 +204,67 @@ function guardarAgente() if (!$id_usuario) { 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"); } - // 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 = ?"; $stmtAgencia = sqlsrv_query($conn, $sqlAgencia, [$_SESSION['usuario_id']]); - $agenciaRow = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC); - $id_agencia = $agenciaRow['id_agencia'] ?? null; + + 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); + $id_agencia = $agenciaRow['id_agencia'] ?? null; if (!$id_agencia) { 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"); } - // 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 (id_agente, id_agencia, fecha_asignacion, activo, asignado_por) VALUES (?, ?, GETDATE(), 1, ?) "; $paramsRelacion = [$id_usuario, $id_agencia, $_SESSION['usuario_id']]; - $stmtRelacion = sqlsrv_query($conn, $sqlRelacion, $paramsRelacion); + + 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); if ($stmtRelacion === false) { 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)); 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)) { 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"); } - // 9. Enviar correo de bienvenida + error_log("Transacción confirmada exitosamente"); + + // 8. Enviar correo de bienvenida if (class_exists('PHPMailer\PHPMailer\PHPMailer')) { $mail = new PHPMailer(true); try { @@ -253,25 +317,30 @@ function guardarAgente() "; $mail->send(); + error_log("Correo enviado exitosamente"); } catch (Exception $e) { 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"); exit; } catch (Exception $e) { error_log("ERROR CAPTURADO: " . $e->getMessage()); + error_log("Stack trace: " . $e->getTraceAsString()); + if (!sqlsrv_rollback($conn)) { error_log("ERROR: No se pudo revertir la transacción"); + error_log(print_r(sqlsrv_errors(), true)); } else { 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"); exit; } finally { diff --git a/app/controllers/patente.php b/app/controllers/patente.php index 0d677bb..2e150bc 100644 --- a/app/controllers/patente.php +++ b/app/controllers/patente.php @@ -241,7 +241,7 @@ function guardar() sqlsrv_free_stmt($stmt); sqlsrv_close($conn); - header("Location: /IMPORTADORES/patente/lista?success=1"); + header("Location: /IMPORTADORES/patente/lista?created=ok"); exit; } @@ -317,13 +317,32 @@ function actualizar() $curp = trim($_POST['curp'] ?? ''); $razon_social = trim($_POST['razon_social'] ?? ''); $id_usuario = $_SESSION['usuario_id']; + $tipo_usuario = $_SESSION['tipo_usuario']; - // ✅ NUEVO: Obtener la agencia actual del usuario - $stmt = sqlsrv_query($conn, "SELECT id_agencia FROM dbo.agente_agencia WHERE id_agente = ?", [$id_usuario]); - + // Obtener id_agencia del usuario actual $id_agencia = null; - if ($stmt && $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { - $id_agencia = $row['id_agencia']; + 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)) { + $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'] ?? ''); @@ -351,6 +370,7 @@ function actualizar() $vae_final = trim($_POST['vae_final'] ?? ''); $vae_siguiente = trim($_POST['vae_siguiente'] ?? ''); + // Validaciones if (!$id_agente || empty($aduana) || empty($patente) || empty($agente_aduanal) || empty($rfc) || empty($curp) || empty($razon_social)) { 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($curp) > 18) die("❌ El CURP no puede exceder 18 caracteres."); - // Validar duplicado (excepto el propio id) - $sqlCheck = "SELECT COUNT(*) as count FROM dbo.agentes_aduanales WHERE aduana = ? AND patente = ? AND id_usuario = ? AND id_agente != ?"; - $stmtCheck = sqlsrv_query($conn, $sqlCheck, [$aduana, $patente, $id_usuario, $id_agente]); + // Validar duplicado + $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_agencia, $id_agente]); if ($stmtCheck === false) die("❌ Error al verificar duplicados."); $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."); - sqlsrv_free_stmt($stmtCheck); - - // Convertir valores vacíos a NULL para la base de datos + // Convertir valores vacíos a NULL $vp_inicio = empty($vp_inicio) ? null : $vp_inicio; $vp_final = empty($vp_final) ? null : $vp_final; $vp_siguiente = empty($vp_siguiente) ? null : $vp_siguiente; @@ -399,7 +416,7 @@ function actualizar() vcc_inicio = ?, vcc_final = ?, vcc_siguiente = ?, vae_inicio = ?, vae_final = ?, vae_siguiente = ?, id_agencia = ? WHERE id_agente = ? - AND id_usuario = ? + AND id_agencia = ? "; $params = [ $aduana, $patente, $agente_aduanal, $rfc, $curp, $razon_social, @@ -409,11 +426,19 @@ function actualizar() $vat_pb_inicio, $vat_pb_final, $vat_pb_siguiente, $vcc_inicio, $vcc_final, $vcc_siguiente, $vae_inicio, $vae_final, $vae_siguiente, $id_agencia, - $id_agente, $id_usuario + $id_agente, $id_agencia ]; $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_close($conn); diff --git a/app/controllers/proveedores.php b/app/controllers/proveedores.php index b8292f4..d8f4ac9 100644 --- a/app/controllers/proveedores.php +++ b/app/controllers/proveedores.php @@ -1,4 +1,5 @@ $user, 'password' => $pass]); - error_log("[getApiToken] POST $url → $body"); + error_log("[getApiToken] POST $url"); $ch = curl_init($url); curl_setopt_array($ch, [ @@ -46,7 +47,7 @@ function getApiToken(): ?string $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); 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'])) { // 3) Guardamos token y tiempo actual @@ -87,62 +88,82 @@ function ajax_lista() $token = getApiToken(); if (!$token) { error_log('[ajax_lista] Sin token válido'); - // Devolvemos estructura vacía - echo json_encode(['data' => []]); + echo json_encode(['data' => [], 'error' => 'No se pudo obtener token de autenticación']); 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'] ?? '', '/'); - $url = $apiBase . '/proveedores'; + $url = $apiBase . '/api/proveedores'; // Cambié para que sea consistente error_log("[ajax_lista] GET $url"); // 4) Ejecutamos cURL $ch = curl_init($url); 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_TIMEOUT => 5, + CURLOPT_TIMEOUT => 10, // Aumenté el timeout ]); $resp = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($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 = []; - if ($status === 200 && ($json = json_decode($resp, true)) && is_array($json)) { - foreach ($json as $p) { - $clave = htmlspecialchars($p['Clave'] ?? '', ENT_QUOTES); + foreach ($json as $p) { + $clave = htmlspecialchars($p['Clave'] ?? '', ENT_QUOTES); - // Construir dirección - $direccion = trim(implode(', ', array_filter([ - $p['Calles'] ?? '', - 'Num. Ext: ' . ($p['NumExt'] ?? ''), - 'Num. Int: ' . ($p['NumInt'] ?? ''), - $p['Colonia'] ?? '', - $p['Municipio'] ?? '', - $p['Ciudad'] ?? '', - 'C.P. ' . ($p['CodigoPostal'] ?? ''), - $p['EntidadFederativa'] ?? '', - $p['Pais'] ?? '' - ]))); + // Construir dirección de forma más robusta + $direccionParts = array_filter([ + $p['Calles'] ?? '', + ($p['NumExt'] ?? '') ? 'Num. Ext: ' . $p['NumExt'] : '', + ($p['NumInt'] ?? '') ? 'Num. Int: ' . $p['NumInt'] : '', + $p['Colonia'] ?? '', + $p['Municipio'] ?? '', + $p['Ciudad'] ?? '', + ($p['CodigoPostal'] ?? '') ? 'C.P. ' . $p['CodigoPostal'] : '', + $p['EntidadFederativa'] ?? '', + $p['Pais'] ?? '' + ]); + + $direccion = implode(', ', $direccionParts); - $dataList[] = [ - $clave, - htmlspecialchars($p['Nombre'] ?? '', ENT_QUOTES), - htmlspecialchars($p['RFC'] ?? '', ENT_QUOTES), - htmlspecialchars($p['Ciudad'] ?? '', ENT_QUOTES), - htmlspecialchars($p['Telefono'] ?? '', ENT_QUOTES), - htmlspecialchars($direccion ?? '', ENT_QUOTES), - // Acciones - "✏️ - " - ]; - } - } else { - error_log('[ajax_lista] Respuesta inválida o status != 200'); + $dataList[] = [ + $clave, + htmlspecialchars($p['Nombre'] ?? '', ENT_QUOTES), + htmlspecialchars($p['RFC'] ?? '', ENT_QUOTES), + htmlspecialchars($p['Ciudad'] ?? '', ENT_QUOTES), + htmlspecialchars($p['Telefono'] ?? '', ENT_QUOTES), + htmlspecialchars($direccion, ENT_QUOTES), + "✏️ + " + ]; } // 6) Devolvemos siempre HTTP 200 con data (posiblemente vacío) @@ -161,20 +182,24 @@ function eliminar() exit; } + // CORRECCIÓN: Usar la misma URL base y corregir el Authorization header $url = rtrim($_ENV['API_URL'] ?? '', '/') . '/api/proveedores/' . urlencode($clave); error_log("[eliminar] DELETE $url"); $ch = curl_init($url); curl_setopt_array($ch, [ 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_TIMEOUT => 5, ]); - curl_exec($ch); + $resp = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); + error_log("[eliminar] HTTP $status: $resp"); + header('Location: /IMPORTADORES/proveedores?deleted=' . ($status===200||$status===204 ? 'ok':'error')); exit; } @@ -203,16 +228,16 @@ function guardar() // 2) Recoger datos del formulario $payload = [ - 'Clave' => trim($_POST['Clave'] ?? ''), - 'Nombre' => trim($_POST['Nombre'] ?? ''), - 'RFC' => trim($_POST['RFC'] ?? ''), - 'Ciudad' => trim($_POST['Ciudad'] ?? ''), - 'Telefono' => trim($_POST['Telefono'] ?? ''), - 'Pais' => trim($_POST['Pais'] ?? ''), - 'Colonia' => trim($_POST['Colonia'] ?? ''), - 'Municipio' => trim($_POST['Municipio'] ?? ''), - 'CodigoPostal' => trim($_POST['CodigoPostal'] ?? ''), - 'EntidadFederativa' => trim($_POST['EntidadFederativa'] ?? ''), + 'Clave' => trim($_POST['Clave'] ?? ''), + 'Nombre' => trim($_POST['Nombre'] ?? ''), + 'RFC' => trim($_POST['RFC'] ?? ''), + 'Ciudad' => trim($_POST['Ciudad'] ?? ''), + 'Telefono' => trim($_POST['Telefono'] ?? ''), + 'Pais' => trim($_POST['Pais'] ?? ''), + 'Colonia' => trim($_POST['Colonia'] ?? ''), + 'Municipio' => trim($_POST['Municipio'] ?? ''), + 'CodigoPostal' => trim($_POST['CodigoPostal'] ?? ''), + 'EntidadFederativa' => trim($_POST['EntidadFederativa'] ?? ''), ]; // 3) Validar campos obligatorios @@ -232,7 +257,7 @@ function guardar() // 5) Llamada a la API para crear el proveedor $url = rtrim($_ENV['API_URL'] ?? '', '/') . '/api/proveedores'; - $ch = curl_init($url); + $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ diff --git a/app/controllers/transportes.php b/app/controllers/transportes.php index dbdd89d..a5aefe2 100644 --- a/app/controllers/transportes.php +++ b/app/controllers/transportes.php @@ -73,7 +73,7 @@ function guardar() $vehiculo = trim($_POST['vehiculo'] ?? ''); $identFiscal = trim($_POST['identificador_fiscal'] ?? ''); - $idTrans = $_POST['id_transportista'] ?? null; + $idTrans = $_POST['transportista_id'] ?? null; if ($vehiculo === '' || $identFiscal === '' || !$idTrans) { die("❌ Todos los campos son obligatorios."); diff --git a/app/controllers/transportistas.php b/app/controllers/transportistas.php index 19a1429..e5f2d23 100644 --- a/app/controllers/transportistas.php +++ b/app/controllers/transportistas.php @@ -40,7 +40,7 @@ function guardar() } // Redirigir de vuelta a la lista o mostrar mensaje… - header("Location: /IMPORTADORES/transportistas/lista?success=1"); + header("Location: /IMPORTADORES/transportistas/lista?created=ok"); exit; } @@ -113,7 +113,7 @@ function lista() include __DIR__ . '/../../views/transportistas/lista.php'; } - /** Descarga la plantilla CSV para carga masiva **/ +/** Descarga la plantilla CSV para carga masiva **/ function template() { $file = __DIR__ . '/../../public/downloads/transportistas_template.csv'; diff --git a/views/admin/agencias_activas.php b/views/admin/agencias_activas.php index afc1f00..e81380b 100644 --- a/views/admin/agencias_activas.php +++ b/views/admin/agencias_activas.php @@ -129,6 +129,16 @@ + + + + diff --git a/views/admin/aprobar_agencias.php b/views/admin/aprobar_agencias.php index 8865f66..3f524dc 100644 --- a/views/admin/aprobar_agencias.php +++ b/views/admin/aprobar_agencias.php @@ -127,6 +127,30 @@ }); }); + + + + \ No newline at end of file diff --git a/views/agencias/alta_agentes.php b/views/agencias/alta_agentes.php index 25ecf15..2cfefa6 100644 --- a/views/agencias/alta_agentes.php +++ b/views/agencias/alta_agentes.php @@ -61,6 +61,99 @@ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } .btn-animated:hover::before { left: 100%; } 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; } @@ -70,26 +163,38 @@

👨‍✈️ Administración de Agentes

-
+
➕ Nuevo Agente
-
- +
+
+ + +
-
- +
+
+ + +
-
- +
+
+ + +
-
- +
+
+ + +
-
+
@@ -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 + }); + }); + + @@ -68,100 +182,128 @@
- -
- - -
+
+ +
+ +
+ + +
+
-
-
+
- +
+ + +
-
+
- +
+ + +
-
-
-
+
- +
+ + +
-
+
- +
+ + +
-
+
- +
+ + +
-
-
-
+
- +
+ + +
-
+
- +
+ + +
+
+ + +
+ +
+ +
+
+ + +
+
+ + Foto Chofer + + Sin foto + +
+ + +
+
+
+ > + +
+
- -
-
- - Foto Chofer - - Sin foto - +
+ + Cancelar
- - -
- - -
- - -
- > - -
- - - Cancelar
diff --git a/views/locaciones/alta_locaciones.php b/views/locaciones/alta_locaciones.php index 51a7c8e..b59d961 100644 --- a/views/locaciones/alta_locaciones.php +++ b/views/locaciones/alta_locaciones.php @@ -64,6 +64,51 @@ if ($tipoUsuario === 'agente_aduanal') { background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } .btn-animated:hover::before { left: 100%; } .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); } } @@ -73,15 +118,15 @@ if ($tipoUsuario === 'agente_aduanal') {
-
+

➕ Nuevo Estado


-
+


-
+
@@ -108,25 +153,25 @@ if ($tipoUsuario === 'agente_aduanal') {
-
+

➕ Nueva Ciudad

-
+

-
+

-
+
@@ -156,12 +201,12 @@ if ($tipoUsuario === 'agente_aduanal') { // Función para cargar estados function cargarEstados(paisId, selectElement) { selectElement.innerHTML = ''; - selectElement.disabled = true; + selectElement.disabled = true; fetch(`/IMPORTADORES/locaciones/estados?pais=${paisId}`) .then(response => response.json()) .then(estados => { - selectElement.innerHTML = ''; + selectElement.innerHTML = ''; estados.forEach(estado => { const option = new Option(estado.nombre, estado.id_estado); selectElement.add(option); @@ -176,14 +221,14 @@ if ($tipoUsuario === 'agente_aduanal') { // Evento para cargar estados cuando se selecciona país en formulario de ciudad document.getElementById('paisCiudad').addEventListener('change', function(e) { - const paisId = e.target.value; + const paisId = e.target.value; const estadoSelect = document.getElementById('estadoCiudad'); if (paisId) { cargarEstados(paisId, estadoSelect); } else { - estadoSelect.innerHTML = ''; - estadoSelect.disabled = true; + estadoSelect.innerHTML = ''; + estadoSelect.disabled = true; } }); @@ -192,28 +237,20 @@ if ($tipoUsuario === 'agente_aduanal') { e.preventDefault(); // Obtener valores directamente - const paisSelect = document.getElementById('paisEstado'); + const paisSelect = document.getElementById('paisEstado'); const entidadInput = document.getElementById('entidadEstado'); - const paisValue = paisSelect.value; + const paisValue = paisSelect.value; const entidadValue = entidadInput.value.trim(); // Validación en frontend if (!paisValue) { - Swal.fire({ - icon: 'warning', - title: 'Campo requerido', - text: 'Debe seleccionar un país' - }); + Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un país' }); return; } if (!entidadValue) { - Swal.fire({ - icon: 'warning', - title: 'Campo requerido', - text: 'Debe ingresar el nombre del estado' - }); + Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe ingresar el nombre del estado' }); return; } @@ -222,11 +259,11 @@ if ($tipoUsuario === 'agente_aduanal') { formData.append('pais', paisValue); formData.append('entidad', entidadValue); - const submitBtn = this.querySelector('button[type="submit"]'); + const submitBtn = this.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; submitBtn.innerHTML = ' Guardando...'; - submitBtn.disabled = true; + submitBtn.disabled = true; // Debug: mostrar lo que se va a enviar console.log('Enviando:', { @@ -241,33 +278,19 @@ if ($tipoUsuario === 'agente_aduanal') { .then(response => response.json()) .then(data => { if (data.success) { - Swal.fire({ - icon: 'success', - title: '¡Éxito!', - text: data.message, - timer: 2000, - showConfirmButton: false - }); + Swal.fire({ icon: 'success', title: '¡Éxito!', text: data.message, timer: 2000, showConfirmButton: false }); this.reset(); } else { - Swal.fire({ - icon: 'error', - title: 'Error', - text: data.message - }); + Swal.fire({ icon: 'error', title: 'Error', text: data.message }); } }) .catch(error => { console.error('Error:', error); - Swal.fire({ - icon: 'error', - title: 'Error', - text: 'Ocurrió un error al procesar la solicitud' - }); + Swal.fire({ icon: 'error', title: 'Error', text: 'Ocurrió un error al procesar la solicitud' }); }) .finally(() => { submitBtn.innerHTML = originalText; - submitBtn.disabled = false; + submitBtn.disabled = false; }); }); @@ -275,12 +298,12 @@ if ($tipoUsuario === 'agente_aduanal') { document.getElementById('ciudadForm').addEventListener('submit', function(e) { e.preventDefault(); - const formData = new FormData(this); - const submitBtn = this.querySelector('button[type="submit"]'); + const formData = new FormData(this); + const submitBtn = this.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; submitBtn.innerHTML = ' Guardando...'; - submitBtn.disabled = true; + submitBtn.disabled = true; fetch('/IMPORTADORES/locaciones/guardarCiudad', { method: 'POST', @@ -289,36 +312,60 @@ if ($tipoUsuario === 'agente_aduanal') { .then(response => response.json()) .then(data => { if (data.success) { - Swal.fire({ - icon: 'success', - title: '¡Éxito!', - text: data.message, - timer: 2000, - showConfirmButton: false - }); + Swal.fire({ icon: 'success', title: '¡Éxito!', text: data.message, timer: 2000, showConfirmButton: false }); this.reset(); // Resetear el select de estados document.getElementById('estadoCiudad').innerHTML = ''; - document.getElementById('estadoCiudad').disabled = true; + document.getElementById('estadoCiudad').disabled = true; } else { - Swal.fire({ - icon: 'error', - title: 'Error', - text: data.message - }); + Swal.fire({ icon: 'error', title: 'Error', text: data.message }); } }) .catch(error => { console.error('Error:', error); - Swal.fire({ - icon: 'error', - title: 'Error', - text: 'Ocurrió un error al procesar la solicitud' - }); + Swal.fire({ icon: 'error', title: 'Error', text: 'Ocurrió un error al procesar la solicitud' }); }) .finally(() => { 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; + } + }); }); }); diff --git a/views/locaciones/gestion_locaciones.php b/views/locaciones/gestion_locaciones.php index 6a2465f..5b5264c 100644 --- a/views/locaciones/gestion_locaciones.php +++ b/views/locaciones/gestion_locaciones.php @@ -588,7 +588,7 @@ if ($tipoUsuario === 'agente_aduanal') { .finally(() => { // RESTAURAR BOTÓN submitBtn.innerHTML = originalText; - submitBtn.disabled = false; + submitBtn.disabled = false; }); }); diff --git a/views/partials/sidebar_administrador.php b/views/partials/sidebar_administrador.php index 3a7cfe9..f84ecf5 100644 --- a/views/partials/sidebar_administrador.php +++ b/views/partials/sidebar_administrador.php @@ -26,7 +26,7 @@ if ($esDashboard): ?> Bienvenido, - Cerrar Sesión + Cerrar Sesión
diff --git a/views/partials/sidebar_agencia.php b/views/partials/sidebar_agencia.php index 772d04d..2715c2d 100644 --- a/views/partials/sidebar_agencia.php +++ b/views/partials/sidebar_agencia.php @@ -26,7 +26,7 @@ if ($esDashboard): ?> Bienvenido, - Cerrar Sesión + Cerrar Sesión
diff --git a/views/partials/sidebar_agente.php b/views/partials/sidebar_agente.php index 8ed44c7..c173c8a 100644 --- a/views/partials/sidebar_agente.php +++ b/views/partials/sidebar_agente.php @@ -26,7 +26,7 @@ if ($esDashboard): ?> Bienvenido, - Cerrar Sesión + Cerrar Sesión
diff --git a/views/partials/sidebar_configuracion.php b/views/partials/sidebar_configuracion.php index d8812d8..d04d5ec 100644 --- a/views/partials/sidebar_configuracion.php +++ b/views/partials/sidebar_configuracion.php @@ -172,7 +172,7 @@ function obtenerUrlPermiso($permiso) { default => '/IMPORTADORES/importadores/dashboard' }; ?> - + ← Panel Principal
diff --git a/views/partials/sidebar_importador.php b/views/partials/sidebar_importador.php index 2794077..8fd77f5 100644 --- a/views/partials/sidebar_importador.php +++ b/views/partials/sidebar_importador.php @@ -24,7 +24,7 @@ if ($esDashboard): ?> Bienvenido, - Cerrar Sesión + Cerrar Sesión
diff --git a/views/patente/alta_patente.php b/views/patente/alta_patente.php index 40cee20..226eb6d 100644 --- a/views/patente/alta_patente.php +++ b/views/patente/alta_patente.php @@ -89,6 +89,51 @@ if ($tipoUsuario === 'agente_aduanal') { .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%; } + /* ========== 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); } } @@ -119,67 +164,67 @@ if ($tipoUsuario === 'agente_aduanal') {

Datos de la Patente / Aduana

-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+

-
+

Capturar para el llenado de la Manifestación de Valor

Datos del Agente Aduanal:

-
+
-
+
-
+
-
+
-
+
@@ -191,107 +236,107 @@ if ($tipoUsuario === 'agente_aduanal') {

Números de Validación y Folios

Números de Validación para Pedimentos

-
+
-
+
-
+
-
+

Números de Validación para Pago Electrónico

-
+
-
+
-
+
-
+

Números de Validación para Avisos Traslado / Plantas-Bodegas

-
+
-
+
-
+
-
+

Números de Validación para Cartas Cupo

-
+
-
+
-
+
-
+

Números de Validación para Avisos Electrónicos

-
+
-
+
-
+
-
+
-
+
Cancelar
@@ -421,6 +466,43 @@ if ($tipoUsuario === 'agente_aduanal') { // SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO 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; + } + }); + }); + }); diff --git a/views/patente/editar.php b/views/patente/editar.php index a120033..f351e07 100644 --- a/views/patente/editar.php +++ b/views/patente/editar.php @@ -86,6 +86,51 @@ if ($tipoUsuario === 'agente_aduanal') { .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%; } + /* ========== 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); } } @@ -118,75 +163,75 @@ if ($tipoUsuario === 'agente_aduanal') {

Datos de la Patente / Aduana

-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+

-
+

Capturar para el llenado de la Manifestación de Valor

Datos del Agente Aduanal:

-
+
-
+
-
+
-
+
-
+
-
+
@@ -199,22 +244,22 @@ if ($tipoUsuario === 'agente_aduanal') {

Números de Validación y Folios

Números de Validación para Pedimentos

-
+
-
+
-
+
-
+
@@ -222,22 +267,22 @@ if ($tipoUsuario === 'agente_aduanal') {

Números de Validación para Pago Electrónico

-
+
-
+
-
+
-
+
@@ -245,22 +290,22 @@ if ($tipoUsuario === 'agente_aduanal') {

Números de Validación para Avisos Traslado / Plantas-Bodegas

-
+
-
+
-
+
-
+
@@ -268,22 +313,22 @@ if ($tipoUsuario === 'agente_aduanal') {

Números de Validación para Cartas Cupo

-
+
-
+
-
+
-
+
@@ -291,22 +336,22 @@ if ($tipoUsuario === 'agente_aduanal') {

Números de Validación para Avisos Electrónicos

-
+
-
+
-
+
-
+
@@ -314,7 +359,7 @@ if ($tipoUsuario === 'agente_aduanal') {
-
+
Cancelar
diff --git a/views/patente/lista.php b/views/patente/lista.php index 81ce5c1..cd03a61 100644 --- a/views/patente/lista.php +++ b/views/patente/lista.php @@ -110,8 +110,8 @@ if ($tipoUsuario === 'agente_aduanal') { - ✏️ - + ✏️ + @@ -147,13 +147,13 @@ if ($tipoUsuario === 'agente_aduanal') { }); - Swal.fire('¡Hecho!','Agente eliminado.','success'); + Swal.fire('¡Hecho!','Patente eliminada.','success'); - Swal.fire('¡Listo!','Agente creado.','success'); + Swal.fire('¡Listo!','Patente creado.','success'); - Swal.fire('¡Listo!','Agente actualizado.','success'); + Swal.fire('¡Listo!','Patente actualizada.','success'); diff --git a/views/productos_frecuentes/alta.php b/views/productos_frecuentes/alta.php index a512de4..1ae708f 100644 --- a/views/productos_frecuentes/alta.php +++ b/views/productos_frecuentes/alta.php @@ -28,8 +28,11 @@ - + + + + @@ -67,6 +70,51 @@ 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); } } @@ -87,23 +135,23 @@

➕ Alta de Producto Frecuente

-
+
-
+
-
+
-
+
-
+
@@ -115,7 +163,7 @@
-
+
@@ -135,7 +183,7 @@
-
+
@@ -174,29 +222,28 @@
- - - - -
-
+ + + +
+
-
+
-
+
@@ -209,7 +256,7 @@ -
+
Cancelar
@@ -218,10 +265,12 @@
- - - + + + + + + - + \ No newline at end of file diff --git a/views/productos_frecuentes/lista.php b/views/productos_frecuentes/lista.php index 827bca5..e538890 100644 --- a/views/productos_frecuentes/lista.php +++ b/views/productos_frecuentes/lista.php @@ -28,8 +28,8 @@ .card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; } .btn-pulse { animation: pulse 2s infinite; } @keyframes pulse { - 0% { transform: scale(1); } - 50% { transform: scale(1.05); } + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .fade-in-up { animation: fadeInUp 0.8s ease-out; } @@ -171,10 +171,10 @@ diff --git a/views/reset/change_password.php b/views/reset/change_password.php index 754c839..3d0d033 100644 --- a/views/reset/change_password.php +++ b/views/reset/change_password.php @@ -98,8 +98,12 @@ $dos_factores_estado = obtenerEstadoDosFactores();

Cambiar contraseña

-

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.



- Cambiar contraseña +

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.


+ + +
diff --git a/views/reset/verificar_codigo.php b/views/reset/verificar_codigo.php index a576989..a0acbaf 100644 --- a/views/reset/verificar_codigo.php +++ b/views/reset/verificar_codigo.php @@ -179,8 +179,12 @@ $dos_factores_estado = obtenerEstadoDosFactores();

Cambiar contraseña

-

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.



- Cambiar contraseña +

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.


+
+ +
diff --git a/views/solicitud_importacion/crear.php b/views/solicitud_importacion/crear.php index 72613af..7d65db1 100644 --- a/views/solicitud_importacion/crear.php +++ b/views/solicitud_importacion/crear.php @@ -58,6 +58,80 @@ .btn-animated:hover::before { left: 100%; } .hide { display: none !important; } .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; } @@ -84,11 +158,11 @@
-
+
-
+
@@ -156,7 +230,7 @@
-
+
@@ -194,9 +268,9 @@
-
+
- +
@@ -221,9 +295,9 @@ - - - + + +
- - + +
- - - + + +
- +
+ +
-
+
- - Cancelar +
+ + Cancelar +
@@ -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; + } + }); + }); + }); diff --git a/views/solicitud_importacion/editar.php b/views/solicitud_importacion/editar.php index deae253..e59c877 100644 --- a/views/solicitud_importacion/editar.php +++ b/views/solicitud_importacion/editar.php @@ -59,6 +59,80 @@ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } .btn-animated:hover::before { left: 100%; } .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; } @@ -71,12 +145,12 @@
-
+
-
+
@@ -150,7 +224,7 @@
-
+
@@ -193,7 +267,7 @@
- +
@@ -219,9 +293,9 @@ $p): ?> - - - + + + - - + + - - + + - - - + + + - - + + >
- - Cancelar + +
+ + Cancelar +
@@ -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; + } + }); + }); + }); diff --git a/views/solicitud_importacion/lista.php b/views/solicitud_importacion/lista.php index c625382..613a246 100644 --- a/views/solicitud_importacion/lista.php +++ b/views/solicitud_importacion/lista.php @@ -162,8 +162,8 @@ ✏️ - @@ -223,10 +223,10 @@ // Al cambiar el select de status $('#tabla-solicitudes').on('change', '.status-select', function () { - const $sel = $(this); - const id = $sel.data('id'); - const status = $sel.val(); - const $row = $sel.closest('tr'); + const $sel = $(this); + const id = $sel.data('id'); + const status = $sel.val(); + const $row = $sel.closest('tr'); const oldStatus = $sel.data('old-status') || $sel.find('option:first').val(); // Guardar el estado anterior para poder revertir si falla @@ -238,8 +238,8 @@ // Mostrar toast de procesando Swal.fire({ title: 'Actualizando...', - text: 'Procesando cambio de estado', - icon: 'info', + text: 'Procesando cambio de estado', + icon: 'info', allowOutsideClick: false, showConfirmButton: false, timer: 1000, @@ -273,8 +273,8 @@ if (res.numeroPedimentoLocal) { Swal.fire({ title: '¡Importación solicitada!', - text: `Se generó el número de pedimento: ${res.numeroPedimentoLocal}`, - icon: 'success', + text: `Se generó el número de pedimento: ${res.numeroPedimentoLocal}`, + icon: 'success', confirmButtonText: 'Entendido' }); @@ -288,15 +288,15 @@ Swal.fire({ title: 'Estado actualizado', text: res.warning, - icon: 'warning', + icon: 'warning', confirmButtonText: 'Entendido' }); } else { // Éxito normal Swal.fire({ title: '¡Actualizado!', - text: 'Estado cambiado correctamente', - icon: 'success', + text: 'Estado cambiado correctamente', + icon: 'success', timer: 2000, showConfirmButton: false }); @@ -408,11 +408,11 @@ Swal.fire({ title: `¿Solicitar importación para ${selected.length} solicitudes?`, - text: 'Se actualizará el estatus a "Solicitar importación" (2)', - icon: 'warning', + text: 'Se actualizará el estatus a "Solicitar importación" (2)', + icon: 'warning', showCancelButton: true, confirmButtonText: 'Sí, proceder', - cancelButtonText: 'Cancelar' + cancelButtonText: 'Cancelar' }).then(result => { if (result.isConfirmed) { $.ajax({ @@ -435,10 +435,6 @@ } }); }); - - - - }); diff --git a/views/transportes/crear.php b/views/transportes/crear.php index 0c2ee48..d169943 100644 --- a/views/transportes/crear.php +++ b/views/transportes/crear.php @@ -66,115 +66,104 @@ .modal-dialog { z-index: 10000 !important; position: relative; } /* Opcional: Mejorar la apariencia del overlay */ .modal-backdrop.show { opacity: 0.5; } - /* ========== ANIMACIONES PARA INPUTS ========== */ - /* 1. Animación al hacer focus */ - .form-control { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; } - .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); } - /* 2. Animación al escribir */ - .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; } + /* ========== 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); } } - /* 4. Floating labels con animación */ + /* 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; } - /* 5. Efecto de pulso en hover */ - .input-pulse:hover { animation: inputPulse 0.6s ease-in-out; } + .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); } } - /* 6. Gradiente animado en el borde */ - .input-gradient { position: relative; overflow: hidden; } - .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; } - .input-gradient:focus::before { left: 100%; } - /* 7. Efecto de brillo en validación exitosa */ - .form-control.valid { border-color: #198754; background: linear-gradient(45deg, #fff, #f8fff9); animation: successGlow 1s ease-in-out; } + .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); } } - /* 8. Typing effect simulation */ - .input-typing { position: relative; } - .input-typing::after { content: '|'; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0; animation: blink 1s infinite; color: #0d6efd; } - .input-typing:focus::after { opacity: 1; } + .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; } } - /* 9. Efecto de ondas */ - .input-ripple { position: relative; overflow: hidden; } - .input-ripple::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:focus::before { width: 300px; height: 300px; } - /* 10. Slide-up animation para los campos */ + .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(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 ========== */ - /* 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-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; } - /* 2. Animación al hacer focus */ + 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"); } - /* 3. Animación cuando tiene valor seleccionado */ - .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"); padding: 1rem 2.5rem 0.5rem 0.75rem; height: auto; min-height: 3.5rem; } - .form-floating-custom .form-select:focus ~ .form-label, - .form-floating-custom .form-select:not([value=""]) ~ .form-label { top: 0.25rem; font-size: 0.75rem; color: #0d6efd; font-weight: 600; } - /* 5. Efecto de shake en validación para select */ + .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; } - /* 6. Efecto de pulso en hover para select */ .select-pulse:hover { animation: inputPulse 0.6s ease-in-out; } - /* 7. Gradiente animado para select */ .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%; } - /* 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; } - /* 9. Efecto de rotación de la flecha al abrir */ .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"); } - /* 10. Efecto de ondas para select */ .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; } - /* 11. Efecto de scale suave */ .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); } - /* 12. Personalización de opciones (limitado en algunos navegadores) */ .form-select option { padding: 0.5rem; transition: all 0.2s ease; } .form-select option:hover { background-color: #f8f9fa; } - /* 13. Indicador de estado */ .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; } + 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; } - /* Quitar todo tipo de animación y borde para el campo foto */ - .no-animation, - .no-border-style { transition: none !important; border: 1px solid #ccc !important; box-shadow: none !important; background: #fff !important; } - .no-animation:focus { transform: none !important; } + /* ========== 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; } @@ -185,49 +174,51 @@
+
- +
+
- +
- + +
+ + +
+
+ +
+
-
- -
- - -
-
-
-
+
Cancelar
@@ -270,10 +261,10 @@ document.getElementById('formTransCrear').addEventListener('submit', function(e) { e.preventDefault(); - const vehiculo = document.getElementById('vehiculo').value.trim(); - const ident_fiscal = document.getElementById('ident_fiscal').value.trim(); - const transportista = document.getElementById('transportista').value; - const fotoF = document.getElementById('foto').files[0]; + const vehiculo = document.getElementById('vehiculo').value.trim(); + const ident_fiscal = document.getElementById('ident_fiscal').value.trim(); + const transportista_id = document.getElementById('transportista_id').value; + const fotoF = document.getElementById('foto').files[0]; if (!vehiculo) { Swal.fire({ icon: 'warning', title: 'Contenedor requerido', text: 'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545' }); @@ -294,10 +285,12 @@ this.submit(); }); + // Script para manejar las animaciones de validación document.addEventListener('DOMContentLoaded', function() { - const inputs = document.querySelectorAll('.form-control'); - + 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'); @@ -306,7 +299,8 @@ this.classList.remove('valid'); } }); - + + // Efecto shake en campos inválidos input.addEventListener('invalid', function() { this.classList.add('shake'); setTimeout(() => { @@ -314,6 +308,68 @@ }, 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 = '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'); + } + } + } }); diff --git a/views/transportes/editar.php b/views/transportes/editar.php index 987a0b7..9425d55 100644 --- a/views/transportes/editar.php +++ b/views/transportes/editar.php @@ -60,6 +60,104 @@ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } .btn-animated:hover::before { left: 100%; } .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; } @@ -69,54 +167,72 @@
-
- -
- - +
+ +
+
+ + + +
-
- -
- - + +
+ +
+
+ + + +
-
+ +
+ +
+ + +
+
+
+ +

- - - - Sin foto - +
+ + + + Sin foto + +
-
+ +
- -
-
- - +
+ +
-
+ +
Cancelar
-