Productos frecuenes
This commit is contained in:
@@ -5,8 +5,61 @@ require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
// 2) Carga nuestro helper de entorno y dispara la carga de .env
|
||||
require_once __DIR__ . '/../helpers/env.php';
|
||||
|
||||
loadEnv();
|
||||
|
||||
/** Obtiene (y cachea en sesión) el JWT de la API usando las credenciales de $_ENV **/
|
||||
/** Obtiene (y cachea) el JWT de la API usando credenciales de $_ENV
|
||||
* Gestiona expiración: asume 1 hora de vida y renueva si ha pasado. **/
|
||||
function getApiToken(): ?string
|
||||
{
|
||||
// Duración en segundos del token (60 min)
|
||||
$ttl = 3600;
|
||||
|
||||
// 1) Si ya tenemos token y no ha expirado, lo devolvemos
|
||||
if (!empty($_SESSION['api_token']) && !empty($_SESSION['api_token_time'])) {
|
||||
$age = time() - $_SESSION['api_token_time'];
|
||||
if ($age < $ttl) {
|
||||
error_log("[getApiToken] Usando token en caché (edad: {$age}s)");
|
||||
return $_SESSION['api_token'];
|
||||
}
|
||||
error_log("[getApiToken] Token expirado (edad: {$age}s), obteniendo uno nuevo");
|
||||
}
|
||||
|
||||
// 2) Sí o sí hacemos login en la API
|
||||
$url = rtrim($_ENV['API_URL'] ?? '', '/') . '/auth/login';
|
||||
$user = $_ENV['API_USER'] ?? '';
|
||||
$pass = $_ENV['API_PASS'] ?? '';
|
||||
$body = json_encode(['username' => $user, 'password' => $pass]);
|
||||
|
||||
error_log("[getApiToken] POST $url");
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("[getApiToken] HTTP $http");
|
||||
|
||||
if ($http === 200 && ($data = json_decode($resp, true)) && !empty($data['token'])) {
|
||||
// 3) Guardamos token y tiempo actual
|
||||
$_SESSION['api_token'] = $data['token'];
|
||||
$_SESSION['api_token_time'] = time();
|
||||
return $data['token'];
|
||||
}
|
||||
|
||||
// 4) Si no se pudo obtener, devolvemos null
|
||||
error_log('[getApiToken] No se pudo obtener token de la API');
|
||||
return null;
|
||||
}
|
||||
|
||||
/** GET /IMPORTADORES/productos_frecuentes
|
||||
* Muestra el listado **/
|
||||
function index()
|
||||
@@ -14,6 +67,90 @@ function index()
|
||||
lista();
|
||||
}
|
||||
|
||||
function lista()
|
||||
{
|
||||
if (empty($_SESSION['usuario_id'])) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
// === Obtener proveedores desde API ===
|
||||
$token = getApiToken();
|
||||
$proveedoresApi = [];
|
||||
|
||||
if ($token) {
|
||||
$apiBase = rtrim($_ENV['API_URL'] ?? '', '/');
|
||||
$url = $apiBase . '/proveedores';
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($status === 200 && ($json = json_decode($resp, true)) && is_array($json)) {
|
||||
foreach ($json as $p) {
|
||||
$clave = $p['Clave'] ?? '';
|
||||
$nombre = $p['Nombre'] ?? '';
|
||||
if ($clave) {
|
||||
$proveedoresApi[$clave] = $nombre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Consulta de productos frecuentes ===
|
||||
$sql = "SELECT
|
||||
pf.id_producto_frecuente,
|
||||
pf.sinonimo,
|
||||
pf.fraccion,
|
||||
pf.nico,
|
||||
pf.numero_parte,
|
||||
pf.proveedor AS proveedor_guardado,
|
||||
u.descripcion AS unidad_de_medida,
|
||||
por.nombre AS pais_origen_destino,
|
||||
pc.nombre AS pais_comprador_vendedor,
|
||||
pf.uso_mercancia,
|
||||
pf.estado_mercancia,
|
||||
pf.preferencia,
|
||||
pf.frecuencia_uso
|
||||
FROM dbo.productos_frecuentes pf
|
||||
LEFT JOIN dbo.unidades_medida_apendice7 u
|
||||
ON pf.umc_id = u.id
|
||||
LEFT JOIN dbo.paises por
|
||||
ON pf.pais_origen_destino = por.nombre
|
||||
LEFT JOIN dbo.paises pc
|
||||
ON pf.pais_comprador_vendedor = pc.nombre
|
||||
WHERE pf.id_importador = ?
|
||||
AND pf.status = 1
|
||||
ORDER BY pf.fecha_alta DESC
|
||||
";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$_SESSION['usuario_id']]);
|
||||
|
||||
if ($stmt === false) {
|
||||
echo '<pre>', print_r(sqlsrv_errors(), true), '</pre>';
|
||||
exit;
|
||||
}
|
||||
|
||||
// === Construir arreglo de productos con nombre de proveedor ===
|
||||
$productos = [];
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$clave = $row['proveedor_guardado'];
|
||||
$row['nombre_proveedor'] = $proveedoresApi[$clave] ?? 'N/D';
|
||||
$productos[] = $row;
|
||||
}
|
||||
|
||||
// === Cargar la vista con productos y nombres de proveedor ===
|
||||
include __DIR__ . '/../../views/productos_frecuentes/lista.php';
|
||||
}
|
||||
|
||||
function ajax_paises()
|
||||
{
|
||||
// Sólo importadores pueden usarlo
|
||||
@@ -58,7 +195,7 @@ function ajax_proveedores()
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => ["Authorization: $token", 'Accept: application/json'],
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
]);
|
||||
@@ -84,14 +221,30 @@ function ajax_proveedores()
|
||||
exit;
|
||||
}
|
||||
|
||||
function lista()
|
||||
function ajax_unidades()
|
||||
{
|
||||
if (empty($_SESSION['usuario_id'])) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
// Sólo importadores pueden usarlo
|
||||
if (empty($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['results' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/productos_frecuentes/lista.php';
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY descripcion";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
|
||||
$out = ['results' => []];
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$out['results'][] = [
|
||||
'id' => $row['id'],
|
||||
'text' => $row['descripcion']
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
/** GET /IMPORTADORES/productos_frecuentes/alta
|
||||
@@ -117,38 +270,118 @@ function guardar()
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$errores = [];
|
||||
$id_importador = $_SESSION['usuario_id'];
|
||||
|
||||
// Recoger y sanear
|
||||
$sinonimo = trim($_POST['sinonimo'] ?? '');
|
||||
$fraccion = trim($_POST['fraccion'] ?? '');
|
||||
$nico = trim($_POST['nico'] ?? '');
|
||||
$numero_parte = trim($_POST['numero_parte'] ?? null);
|
||||
$descripcion = trim($_POST['descripcion'] ?? null);
|
||||
$numero_parte = trim($_POST['numero_parte'] ?? '');
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$umc_id = intval($_POST['umc_id'] ?? 0);
|
||||
$pais_origen_destino = trim($_POST['pais_origen_destino'] ?? null);
|
||||
$pais_comprador_vendedor = trim($_POST['pais_comprador_vendedor'] ?? null);
|
||||
$uso_mercancia = trim($_POST['uso_mercancia'] ?? null);
|
||||
$estado_mercancia = trim($_POST['estado_mercancia'] ?? null);
|
||||
$vinculacion = trim($_POST['vinculacion'] ?? null);
|
||||
$observaciones = trim($_POST['observaciones'] ?? null);
|
||||
$preferencia = trim($_POST['preferencia'] ?? null);
|
||||
$criterio_preferencia = trim($_POST['criterio_preferencia'] ?? null);
|
||||
$uso_producto = trim($_POST['uso_producto'] ?? null);
|
||||
$descripcion_producto = trim($_POST['descripcion_producto'] ?? null);
|
||||
$certificado_origen = isset($_POST['certificado_origen']) ? 1 : 0;
|
||||
$tipo_mercancia = trim($_POST['tipo_mercancia'] ?? null);
|
||||
$pais_origen_destino = trim($_POST['pais_origen_destino'] ?? '');
|
||||
$pais_comprador_vendedor = trim($_POST['pais_comprador_vendedor'] ?? '');
|
||||
$uso_mercancia = trim($_POST['uso_mercancia'] ?? '');
|
||||
$estado_mercancia = trim($_POST['estado_mercancia'] ?? '');
|
||||
$vinculacion = trim($_POST['vinculacion'] ?? '');
|
||||
$observaciones = trim($_POST['observaciones'] ?? '');
|
||||
$preferencia = trim($_POST['preferencia'] ?? '');
|
||||
$criterio_preferencia = trim($_POST['criterio_preferencia'] ?? '');
|
||||
$uso_producto = trim($_POST['uso_producto'] ?? '');
|
||||
$descripcion_producto = trim($_POST['descripcion_producto'] ?? '');
|
||||
$certificado_origen = trim($_POST['certificado_origen'] ?? '');
|
||||
$tipo_mercancia = trim($_POST['tipo_mercancia'] ?? '');
|
||||
$documento_en_original = isset($_POST['documento_en_original']) ? 1 : 0;
|
||||
$proveedor = trim($_POST['proveedor'] ?? null);
|
||||
$id_importador = intval($_SESSION['usuario_id']);
|
||||
$status = 1;
|
||||
$proveedor = trim($_POST['proveedor'] ?? '');
|
||||
$status = intval($_POST['status'] ?? 1);
|
||||
$frecuencia_uso = intval($_POST['frecuencia_uso'] ?? 1);
|
||||
|
||||
// Validación mínima
|
||||
if ($sinonimo === '' || $fraccion === '' || $nico === '' || $umc_id <= 0) {
|
||||
$_SESSION['flash_error'] = 'Sinónimo, Fracción, NICO y Unidad de Medida son obligatorios.';
|
||||
// Validación de campos obligatorios (según tu formulario)
|
||||
if ($sinonimo === '') {
|
||||
$errores[] = 'El sinónimo es obligatorio';
|
||||
}
|
||||
if ($fraccion === '') {
|
||||
$errores[] = 'La fracción es obligatoria';
|
||||
}
|
||||
if ($nico === '') {
|
||||
$errores[] = 'El NICO es obligatorio';
|
||||
}
|
||||
if ($numero_parte === '') {
|
||||
$errores[] = 'El número de parte es obligatorio';
|
||||
}
|
||||
if ($pais_origen_destino === '') {
|
||||
$errores[] = 'El país de origen/destino es obligatorio';
|
||||
}
|
||||
if ($pais_comprador_vendedor === '') {
|
||||
$errores[] = 'El país comprador/vendedor es obligatorio';
|
||||
}
|
||||
if ($umc_id <= 0) {
|
||||
$errores[] = 'La unidad de medida es obligatoria';
|
||||
}
|
||||
|
||||
// Validar que NICO solo contenga números
|
||||
if (!preg_match('/^[0-9]+$/', $nico)) {
|
||||
$errores[] = 'El NICO solo puede contener números';
|
||||
}
|
||||
|
||||
// **SOLUCIÓN PRINCIPAL: Convertir IDs de países a nombres**
|
||||
$pais_origen_nombre = null;
|
||||
$pais_comprador_nombre = null;
|
||||
|
||||
if (!empty($pais_origen_destino)) {
|
||||
$sql_pais = "SELECT nombre FROM dbo.paises WHERE id_pais = ?";
|
||||
$stmt_pais = sqlsrv_query($conn, $sql_pais, [intval($pais_origen_destino)]);
|
||||
|
||||
if ($stmt_pais && $row = sqlsrv_fetch_array($stmt_pais, SQLSRV_FETCH_ASSOC)) {
|
||||
$pais_origen_nombre = $row['nombre'];
|
||||
} else {
|
||||
$errores[] = 'País de origen/destino no válido';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($pais_comprador_vendedor)) {
|
||||
$sql_pais = "SELECT nombre FROM dbo.paises WHERE id_pais = ?";
|
||||
$stmt_pais = sqlsrv_query($conn, $sql_pais, [intval($pais_comprador_vendedor)]);
|
||||
|
||||
if ($stmt_pais && $row = sqlsrv_fetch_array($stmt_pais, SQLSRV_FETCH_ASSOC)) {
|
||||
$pais_comprador_nombre = $row['nombre'];
|
||||
} else {
|
||||
$errores[] = 'País comprador/vendedor no válido';
|
||||
}
|
||||
}
|
||||
|
||||
// Validar que existe la unidad de medida
|
||||
$sql_umc = "SELECT COUNT(*) as count FROM dbo.unidades_medida_apendice7 WHERE id = ?";
|
||||
$stmt_umc = sqlsrv_query($conn, $sql_umc, [$umc_id]);
|
||||
|
||||
if ($stmt_umc && $row = sqlsrv_fetch_array($stmt_umc, SQLSRV_FETCH_ASSOC)) {
|
||||
if ($row['count'] == 0) {
|
||||
$errores[] = 'Unidad de medida no válida';
|
||||
}
|
||||
}
|
||||
|
||||
// Si hay errores, regresar
|
||||
if (!empty($errores)) {
|
||||
$_SESSION['flash_error'] = implode('. ', $errores);
|
||||
header('Location: /IMPORTADORES/productos_frecuentes/alta');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Convertir valores vacíos a NULL para campos opcionales
|
||||
if ($numero_parte === '') $numero_parte = null;
|
||||
if ($descripcion === '') $descripcion = null;
|
||||
if ($uso_mercancia === '') $uso_mercancia = null;
|
||||
if ($estado_mercancia === '') $estado_mercancia = null;
|
||||
if ($vinculacion === '') $vinculacion = null;
|
||||
if ($observaciones === '') $observaciones = null;
|
||||
if ($preferencia === '') $preferencia = null;
|
||||
if ($criterio_preferencia === '') $criterio_preferencia = null;
|
||||
if ($uso_producto === '') $uso_producto = null;
|
||||
if ($descripcion_producto === '') $descripcion_producto = null;
|
||||
if ($tipo_mercancia === '') $tipo_mercancia = null;
|
||||
if ($proveedor === '') $proveedor = null;
|
||||
|
||||
// INSERT
|
||||
$sql = "INSERT INTO dbo.productos_frecuentes
|
||||
(sinonimo, fraccion, nico, numero_parte, descripcion, umc_id,
|
||||
@@ -160,20 +393,26 @@ function guardar()
|
||||
";
|
||||
$params = [
|
||||
$sinonimo, $fraccion, $nico, $numero_parte, $descripcion, $umc_id,
|
||||
$pais_origen_destino, $pais_comprador_vendedor, $uso_mercancia, $estado_mercancia, $vinculacion,
|
||||
$pais_origen_nombre, $pais_comprador_nombre, $uso_mercancia, $estado_mercancia, $vinculacion,
|
||||
$observaciones, $preferencia, $criterio_preferencia, $uso_producto, $descripcion_producto,
|
||||
$certificado_origen, $tipo_mercancia, $documento_en_original, $proveedor,
|
||||
$id_importador, $status, $frecuencia_uso
|
||||
];
|
||||
|
||||
// Para debug - remover en producción
|
||||
error_log("Datos a insertar: " . print_r($params, true));
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
$_SESSION['flash_error'] = 'Error al guardar el producto frecuente.';
|
||||
$errors = sqlsrv_errors();
|
||||
error_log("Error en SQL: " . print_r($errors, true));
|
||||
$_SESSION['flash_error'] = 'Error al guardar el producto.';
|
||||
header('Location: /IMPORTADORES/productos_frecuentes/alta');
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['flash_success'] = 'Producto frecuente registrado correctamente.';
|
||||
$_SESSION['flash_success'] = 'Producto registrado correctamente.';
|
||||
header('Location: /IMPORTADORES/productos_frecuentes');
|
||||
exit;
|
||||
}
|
||||
@@ -205,6 +444,17 @@ function editar()
|
||||
exit;
|
||||
}
|
||||
|
||||
// Recuperar IDs reales desde nombres de país
|
||||
foreach (['pais_origen_destino', 'pais_comprador_vendedor'] as $campo) {
|
||||
$nombre = $producto[$campo];
|
||||
$stmtPais = sqlsrv_query($conn, "SELECT id_pais FROM dbo.paises WHERE nombre = ?", [$nombre]);
|
||||
if ($stmtPais && ($row = sqlsrv_fetch_array($stmtPais, SQLSRV_FETCH_ASSOC))) {
|
||||
$producto[$campo] = $row['id_pais'];
|
||||
} else {
|
||||
$producto[$campo] = null;
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/productos_frecuentes/editar.php';
|
||||
}
|
||||
|
||||
@@ -219,29 +469,91 @@ function actualizar()
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$id = intval($_POST['id_producto_frecuente'] ?? 0);
|
||||
$sinonimo = trim($_POST['sinonimo'] ?? '');
|
||||
$errores = [];
|
||||
|
||||
$id = intval($_POST['id_producto_frecuente'] ?? 0);
|
||||
$sinonimo = trim($_POST['sinonimo'] ?? '');
|
||||
$fraccion = trim($_POST['fraccion'] ?? '');
|
||||
$nico = trim($_POST['nico'] ?? '');
|
||||
$numero_parte = trim($_POST['numero_parte'] ?? '');
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$umc_id = intval($_POST['umc_id'] ?? 0);
|
||||
$pais_origen_destino = trim($_POST['pais_origen_destino'] ?? '');
|
||||
$pais_comprador_vendedor = trim($_POST['pais_comprador_vendedor'] ?? '');
|
||||
$uso_mercancia = trim($_POST['uso_mercancia'] ?? '');
|
||||
$estado_mercancia = trim($_POST['estado_mercancia'] ?? '');
|
||||
$vinculacion = trim($_POST['vinculacion'] ?? '');
|
||||
$observaciones = trim($_POST['observaciones'] ?? '');
|
||||
$preferencia = trim($_POST['preferencia'] ?? '');
|
||||
$criterio_preferencia = trim($_POST['criterio_preferencia'] ?? '');
|
||||
$uso_producto = trim($_POST['uso_producto'] ?? '');
|
||||
$descripcion_producto = trim($_POST['descripcion_producto'] ?? '');
|
||||
$certificado_origen = trim($_POST['certificado_origen'] ?? '');
|
||||
$tipo_mercancia = trim($_POST['tipo_mercancia'] ?? '');
|
||||
$documento_en_original = isset($_POST['documento_en_original']) ? 1 : 0;
|
||||
$proveedor = trim($_POST['proveedor'] ?? '');
|
||||
|
||||
// Validar países
|
||||
$pais_origen_nombre = null;
|
||||
$pais_comprador_nombre = null;
|
||||
|
||||
if (!empty($pais_origen_destino)) {
|
||||
$sql_pais = "SELECT nombre FROM dbo.paises WHERE id_pais = ?";
|
||||
$stmt_pais = sqlsrv_query($conn, $sql_pais, [intval($pais_origen_destino)]);
|
||||
if ($stmt_pais && ($row = sqlsrv_fetch_array($stmt_pais, SQLSRV_FETCH_ASSOC))) {
|
||||
$pais_origen_nombre = $row['nombre'];
|
||||
} else {
|
||||
$errores[] = 'País de origen/destino no válido';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($pais_comprador_vendedor)) {
|
||||
$sql_pais = "SELECT nombre FROM dbo.paises WHERE id_pais = ?";
|
||||
$stmt_pais = sqlsrv_query($conn, $sql_pais, [intval($pais_comprador_vendedor)]);
|
||||
if ($stmt_pais && ($row = sqlsrv_fetch_array($stmt_pais, SQLSRV_FETCH_ASSOC))) {
|
||||
$pais_comprador_nombre = $row['nombre'];
|
||||
} else {
|
||||
$errores[] = 'País comprador/vendedor no válido';
|
||||
}
|
||||
}
|
||||
|
||||
// Validar unidad
|
||||
$sql_umc = "SELECT COUNT(*) as count FROM dbo.unidades_medida_apendice7 WHERE id = ?";
|
||||
$stmt_umc = sqlsrv_query($conn, $sql_umc, [$umc_id]);
|
||||
if (!$stmt_umc || ($row = sqlsrv_fetch_array($stmt_umc, SQLSRV_FETCH_ASSOC))['count'] == 0) {
|
||||
$errores[] = 'Unidad de medida no válida';
|
||||
}
|
||||
|
||||
// Si hay errores
|
||||
if (!empty($errores)) {
|
||||
$_SESSION['flash_error'] = implode('. ', $errores);
|
||||
header("Location: /IMPORTADORES/productos_frecuentes/editar?id=$id");
|
||||
exit;
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
$sql = "UPDATE dbo.productos_frecuentes SET
|
||||
sinonimo = ?, fraccion = ?, nico = ?, numero_parte = ?, descripcion = ?, umc_id = ?,
|
||||
pais_origen_destino = ?, pais_comprador_vendedor = ?, uso_mercancia = ?, estado_mercancia = ?, vinculacion = ?,
|
||||
observaciones = ?, preferencia = ?, criterio_preferencia = ?, uso_producto = ?, descripcion_producto = ?,
|
||||
certificado_origen = ?, tipo_mercancia = ?, documento_en_original = ?, proveedor = ?
|
||||
sinonimo = ?, fraccion = ?, nico = ?, numero_parte = ?, descripcion = ?,
|
||||
umc_id = ?, pais_origen_destino = ?, pais_comprador_vendedor = ?,
|
||||
uso_mercancia = ?, estado_mercancia = ?, vinculacion = ?, observaciones = ?,
|
||||
preferencia = ?, criterio_preferencia = ?, uso_producto = ?, descripcion_producto = ?,
|
||||
certificado_origen = ?, tipo_mercancia = ?, documento_en_original = ?, proveedor = ?
|
||||
WHERE id_producto_frecuente = ?
|
||||
";
|
||||
$params = [
|
||||
$sinonimo, $fraccion, $nico, $numero_parte, $descripcion, $umc_id,
|
||||
$pais_origen_destino, $pais_comprador_vendedor, $uso_mercancia, $estado_mercancia, $vinculacion,
|
||||
$observaciones, $preferencia, $criterio_preferencia, $uso_producto, $descripcion_producto,
|
||||
$certificado_origen, $tipo_mercancia, $documento_en_original, $proveedor, $id
|
||||
$sinonimo, $fraccion, $nico, $numero_parte, $descripcion, $umc_id,
|
||||
$pais_origen_nombre, $pais_comprador_nombre, $uso_mercancia, $estado_mercancia, $vinculacion,
|
||||
$observaciones, $preferencia, $criterio_preferencia, $uso_producto, $descripcion_producto,
|
||||
$certificado_origen, $tipo_mercancia, $documento_en_original, $proveedor, $id
|
||||
];
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
$_SESSION['flash_error'] = 'Error al actualizar el producto frecuente.';
|
||||
$_SESSION['flash_error'] = 'Error al actualizar el producto.';
|
||||
} else {
|
||||
$_SESSION['flash_success'] = 'Producto frecuente actualizado correctamente.';
|
||||
$_SESSION['flash_success'] = 'Producto actualizado correctamente.';
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/productos_frecuentes');
|
||||
exit;
|
||||
}
|
||||
@@ -258,32 +570,6 @@ function importacion_csv()
|
||||
include __DIR__ . '/../../views/productos_frecuentes/importacion_csv.php';
|
||||
}
|
||||
|
||||
function ajax_unidades()
|
||||
{
|
||||
// Sólo importadores pueden usarlo
|
||||
if (empty($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['results' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY descripcion";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
|
||||
$out = ['results' => []];
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$out['results'][] = [
|
||||
'id' => $row['id'],
|
||||
'text' => $row['descripcion']
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
/** POST /IMPORTADORES/productos_frecuentes/procesar_csv
|
||||
* Procesa el upload y la inserción de CSV **/
|
||||
function procesar_csv()
|
||||
@@ -321,3 +607,33 @@ function procesar_csv()
|
||||
header('Location: /IMPORTADORES/productos_frecuentes');
|
||||
exit;
|
||||
}
|
||||
|
||||
function eliminar()
|
||||
{
|
||||
if (empty($_SESSION['usuario_id'])) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
http_response_code(400);
|
||||
echo "ID inválido";
|
||||
return;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "UPDATE productos_frecuentes SET status = 0 WHERE id_producto_frecuente = ? AND id_importador = ?";
|
||||
$params = [$id, $_SESSION['usuario_id']];
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
echo '<pre>', print_r(sqlsrv_errors(), true), '</pre>';
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/productos_frecuentes?deleted=ok');
|
||||
exit;
|
||||
}
|
||||
@@ -931,7 +931,7 @@ function generarPedimentoDesdeSolicitud($conn, $id, $usuarioId, $token)
|
||||
$ch = curl_init($urlPed);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_HTTPHEADER => ["Authorization: $token", 'Content-Type: application/json'],
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Content-Type: application/json'],
|
||||
CURLOPT_POSTFIELDS => $jsonPayload,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
@@ -1025,7 +1025,7 @@ function ajax_lista()
|
||||
// 4) Ejecutamos cURL
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => ["Authorization: $token", 'Accept: application/json'],
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
]);
|
||||
@@ -1332,7 +1332,7 @@ function update_status()
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: $token",
|
||||
"Authorization: Bearer $token",
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_POSTFIELDS => $jsonPayload,
|
||||
@@ -1448,15 +1448,15 @@ function enviarNotificacionCambioStatus($email, $nombreCompleto, $datosSolicitud
|
||||
/** Obtiene información del status (descripción y emoji) **/
|
||||
function obtenerInfoStatus($status) {
|
||||
$statusMap = [
|
||||
1 => ['emoji' => '🔄', 'descripcion' => 'En proceso', 'color' => '#17a2b8'],
|
||||
1 => ['emoji' => '🔄', 'descripcion' => 'En proceso', 'color' => '#17a2b8'],
|
||||
2 => ['emoji' => '📋', 'descripcion' => 'Solicitar importación', 'color' => '#ffc107'],
|
||||
3 => ['emoji' => '🏢', 'descripcion' => 'Con agencia aduana', 'color' => '#6f42c1'],
|
||||
4 => ['emoji' => '💳', 'descripcion' => 'En proceso de pago', 'color' => '#fd7e14'],
|
||||
5 => ['emoji' => '✅', 'descripcion' => 'Pedimento generado', 'color' => '#28a745'],
|
||||
6 => ['emoji' => '📦', 'descripcion' => 'En tránsito', 'color' => '#007bff'],
|
||||
7 => ['emoji' => '🏁', 'descripcion' => 'Entregado', 'color' => '#28a745'],
|
||||
8 => ['emoji' => '❌', 'descripcion' => 'Cancelado', 'color' => '#dc3545'],
|
||||
9 => ['emoji' => '⏸️', 'descripcion' => 'Suspendido', 'color' => '#6c757d']
|
||||
3 => ['emoji' => '🏢', 'descripcion' => 'Con agencia aduanal', 'color' => '#6f42c1'],
|
||||
4 => ['emoji' => '💳', 'descripcion' => 'En proceso de pago', 'color' => '#fd7e14'],
|
||||
5 => ['emoji' => '✅', 'descripcion' => 'Pedimento generado', 'color' => '#28a745'],
|
||||
6 => ['emoji' => '📦', 'descripcion' => 'En tránsito', 'color' => '#007bff'],
|
||||
7 => ['emoji' => '🏁', 'descripcion' => 'Entregado', 'color' => '#28a745'],
|
||||
8 => ['emoji' => '❌', 'descripcion' => 'Cancelado', 'color' => '#dc3545'],
|
||||
9 => ['emoji' => '⏸️', 'descripcion' => 'Suspendido', 'color' => '#6c757d']
|
||||
];
|
||||
|
||||
return $statusMap[$status] ?? [
|
||||
@@ -1703,7 +1703,7 @@ function obtenerProveedorPorClave($clave) {
|
||||
// Ejecutar cURL para obtener todos los proveedores
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => ["Authorization: $token", 'Accept: application/json'],
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", 'Accept: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
]);
|
||||
|
||||
11
productos_frecuentes.txt
Normal file
11
productos_frecuentes.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
-- 1. Elimina la restricción DEFAULT
|
||||
ALTER TABLE [Importaciones_HC].[dbo].[productos_frecuentes]
|
||||
DROP CONSTRAINT [DF__productos__certi__3EDC53F0];
|
||||
|
||||
-- 2. Modifica el tipo de dato
|
||||
ALTER TABLE [Importaciones_HC].[dbo].[productos_frecuentes]
|
||||
ALTER COLUMN [certificado_origen] NVARCHAR(100);
|
||||
|
||||
-- 3. (Opcional) Si quieres volver a agregar un valor por defecto
|
||||
ALTER TABLE [Importaciones_HC].[dbo].[productos_frecuentes]
|
||||
ADD CONSTRAINT DF_productos_certificado_origen DEFAULT '' FOR [certificado_origen];
|
||||
@@ -319,8 +319,7 @@
|
||||
return;
|
||||
}
|
||||
if (!soloNumerosRegex.test(numero_licencia)) {
|
||||
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545'
|
||||
});
|
||||
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('numero_licencia').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,12 +168,14 @@
|
||||
</a>
|
||||
<a href="/IMPORTADORES/productos_frecuentes/editar"
|
||||
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/productos_frecuentes/editar') ? 'active' : '' ?>">
|
||||
• Editar Producto
|
||||
• Ver Productos
|
||||
</a>
|
||||
<!--
|
||||
<a href="/IMPORTADORES/productos_frecuentes/importacion_csv"
|
||||
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/productos_frecuentes/importacion_csv') ? 'active' : '' ?>">
|
||||
• Importación Masiva CSV
|
||||
</a>
|
||||
-->
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -364,12 +366,14 @@
|
||||
</a>
|
||||
<a href="/IMPORTADORES/productos_frecuentes/editar"
|
||||
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/productos_frecuentes/editar') ? 'active' : '' ?>">
|
||||
• Editar Producto
|
||||
• Ver Productos
|
||||
</a>
|
||||
<!--
|
||||
<a href="/IMPORTADORES/productos_frecuentes/importacion_csv"
|
||||
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/productos_frecuentes/importacion_csv') ? 'active' : '' ?>">
|
||||
• Importación Masiva CSV
|
||||
</a>
|
||||
-->
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
<!-- views/productos_frecuentes/alta.php -->
|
||||
<?php
|
||||
include __DIR__ . '/../partials/sidebar_importador.php';
|
||||
|
||||
// Opciones de Vinculación y Criterio Preferencia
|
||||
$vinculaciones = [
|
||||
'0' => 'No existe',
|
||||
'1' => 'Existe, no afecta',
|
||||
'2' => 'Existe y afecta'
|
||||
];
|
||||
$criterios = [
|
||||
'A' => 'Criterio A',
|
||||
'B' => 'Criterio B',
|
||||
'C' => 'Criterio C',
|
||||
'D' => 'Criterio D',
|
||||
'E' => 'Criterio E',
|
||||
'F' => 'Criterio F'
|
||||
];
|
||||
$vinculaciones = [ '0' => 'No existe', '1' => 'Existe, no afecta', '2' => 'Existe y afecta' ];
|
||||
$criterios = [ 'A' => 'Criterio A', 'B' => 'Criterio B', 'C' => 'Criterio C', 'D' => 'Criterio D', 'E' => 'Criterio E', 'F' => 'Criterio F' ];
|
||||
// Proveedores, unidades y países se cargan por AJAX
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
@@ -48,9 +37,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); }
|
||||
100% { transform: scale(1); }
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
@@ -137,27 +125,28 @@
|
||||
<form id="altaProductoFrecuenteForm" action="/IMPORTADORES/productos_frecuentes/guardar" method="POST">
|
||||
<div class="row g-3 form-group-animated">
|
||||
<!-- Sinónimo, Fracción, NICO -->
|
||||
<div class="col-md-4 form-group-animated">
|
||||
<div class="col-md-6 form-group-animated">
|
||||
<label for="sinonimo" class="form-label">Sinónimo *</label>
|
||||
<input type="text" id="sinonimo" name="sinonimo" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-4 form-group-animated">
|
||||
<label for="fraccion" class="form-label">Fracción *</label>
|
||||
<input type="text" id="fraccion" name="fraccion" class="form-control" required>
|
||||
<input type="text" id="fraccion" name="fraccion" class="form-control" maxlegth="8" required>
|
||||
</div>
|
||||
<div class="col-md-4 form-group-animated">
|
||||
<div class="col-md-2 form-group-animated">
|
||||
<label for="nico" class="form-label">NICO *</label>
|
||||
<input type="text" id="nico" name="nico" class="form-control" required>
|
||||
<input type="text" id="nico" name="nico" class="form-control" maxlength="2" required>
|
||||
</div>
|
||||
|
||||
<!-- Número de Parte / Proveedor -->
|
||||
<div class="col-md-6 form-group-animated">
|
||||
<label for="numero_parte" class="form-label">Número de Parte</label>
|
||||
<input type="text" id="numero_parte" name="numero_parte" class="form-control">
|
||||
<div class="col-md-5 form-group-animated">
|
||||
<label for="numero_parte" class="form-label">Número de Parte *</label>
|
||||
<input type="text" id="numero_parte" name="numero_parte" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="proveedor" class="form-label">Proveedor *</label>
|
||||
<select id="proveedor" name="proveedor" class="form-select searchable" required>
|
||||
<div class="col-md-3"></div>
|
||||
<div class="col-md-4">
|
||||
<label for="proveedor" class="form-label">Proveedor</label>
|
||||
<select id="proveedor" name="proveedor" class="form-select searchable">
|
||||
<option value="">Cargando proveedores…</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -169,35 +158,42 @@
|
||||
</div>
|
||||
|
||||
<!-- País Origen/Destino -->
|
||||
<div class="col-md-4">
|
||||
<label for="pais_origen_destino" class="form-label">País Origen/Destino</label>
|
||||
<select id="pais_origen_destino" name="pais_origen_destino" class="form-select searchable" required>
|
||||
<div class="col-md-3">
|
||||
<label for="pais_origen_destino" class="form-label">País Origen/Destino *</label>
|
||||
<select id="pais_origen_destino" name="pais_origen_destino" class="form-select searchable">
|
||||
<option value="">Cargando países…</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- País Comprador/Vendedor -->
|
||||
<div class="col-md-4">
|
||||
<label for="pais_comprador_vendedor" class="form-label">País Comprador/Vendedor</label>
|
||||
<select id="pais_comprador_vendedor" name="pais_comprador_vendedor" class="form-select searchable" required>
|
||||
<div class="col-md-3">
|
||||
<label for="pais_comprador_vendedor" class="form-label">País Comprador/Vendedor *</label>
|
||||
<select id="pais_comprador_vendedor" name="pais_comprador_vendedor" class="form-select searchable">
|
||||
<option value="">Cargando países…</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Uso de la Mercancía -->
|
||||
<div class="col-md-4 form-group-animated">
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<label for="uso_mercancia" class="form-label">Uso de la Mercancía</label>
|
||||
<input type="text" id="uso_mercancia" name="uso_mercancia" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Estado de la Mercancia -->
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<label for="estado_mercancia" class="form-label">Estado de la Mercancía</label>
|
||||
<input type="text" id="estado_mercancia" name="estado_mercancia" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Unidad de Medida -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label for="umc_id" class="form-label">Unidad de Medida *</label>
|
||||
<select id="umc_id" name="umc_id" class="form-select searchable" required>
|
||||
<select id="umc_id" name="umc_id" class="form-select searchable">
|
||||
<option value="">Cargando unidades…</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Vinculación -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label for="vinculacion" class="form-label">Vinculación</label>
|
||||
<select id="vinculacion" name="vinculacion" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -208,7 +204,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Preferencia (fijas) -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label for="preferencia" class="form-label">Preferencia</label>
|
||||
<select id="preferencia" name="preferencia" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -220,8 +216,8 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Criterio Preferencia / Tipo Mercancía -->
|
||||
<div class="col-md-6">
|
||||
<!-- Criterio Preferencia -->
|
||||
<div class="col-md-3">
|
||||
<label for="criterio_preferencia" class="form-label">Criterio Preferencia</label>
|
||||
<!-- Agrega `disabled` al select de criterio -->
|
||||
<select id="criterio_preferencia" name="criterio_preferencia" class="form-select searchable" disabled>
|
||||
@@ -231,18 +227,27 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 form-group-animated">
|
||||
|
||||
<!-- Tipo Mercancía -->
|
||||
<div class="col-md-5 form-group-animated">
|
||||
<label for="tipo_mercancia" class="form-label">Tipo de Mercancía</label>
|
||||
<input type="text" id="tipo_mercancia" name="tipo_mercancia" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2"></div>
|
||||
|
||||
<!-- Checkboxes -->
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="certificado_origen" name="certificado_origen" value="1">
|
||||
<label class="form-check-label" for="certificado_origen">Certificado de Origen</label>
|
||||
</div>
|
||||
<!-- Certificado de Origen -->
|
||||
<div class="col-md-5 form-group-animated">
|
||||
<label for="certificado_origen" class="form-label">Certificado de Origen</label>
|
||||
<input type="text" id="certificado_origen" name="certificado_origen" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Observaciones -->
|
||||
<div class="col-12 form-group-animated">
|
||||
<label for="observaciones" class="form-label">Observaciones</label>
|
||||
<textarea id="observaciones" name="observaciones" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Checkbox -->
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="documento_en_original" name="documento_en_original" value="1">
|
||||
@@ -251,9 +256,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Ocultos -->
|
||||
<input type="hidden" name="id_importador" value="<?= $_SESSION['usuario_id'] ?>">
|
||||
<input type="hidden" name="status" value="1">
|
||||
<input type="hidden" name="frecuencia_uso" value="1">
|
||||
<input type="hidden" name="id_importador" value="<?= $_SESSION['usuario_id'] ?>">
|
||||
<input type="hidden" name="status" value="1">
|
||||
<input type="hidden" name="frecuencia_uso" value="1">
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="col-12 text-end mt-4 form-group-animated">
|
||||
@@ -272,6 +277,88 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<script>
|
||||
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const sinonimo = document.getElementById('sinonimo').value.trim();
|
||||
const fraccion = document.getElementById('fraccion').value.trim();
|
||||
const nico = document.getElementById('nico').value.trim();
|
||||
const numero_parte = document.getElementById('numero_parte').value.trim();
|
||||
const pais_o_d = document.getElementById('pais_origen_destino').value;
|
||||
const pais_c_v = document.getElementById('pais_comprador_vendedor').value;
|
||||
const unidad_medida = document.getElementById('umc_id').value;
|
||||
|
||||
const soloNumRegex = /^[0-9]+$/;
|
||||
|
||||
if (!sinonimo) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa el sinónimo.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('sinonimo').focus();
|
||||
}
|
||||
if (!fraccion) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa la fracción.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('fraccion').focus();
|
||||
}
|
||||
if (!soloNumRegex.test(fraccion)) {
|
||||
Swal.fire({ icon: 'error', title: 'Fracción inválida', text: 'La fracción solo puede contener números', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('fraccion').focus();
|
||||
return;
|
||||
}
|
||||
if (!nico) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa el NICO.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('nico').focus();
|
||||
}
|
||||
if (!soloNumRegex.test(nico)) {
|
||||
Swal.fire({ icon: 'error', title: 'NICO inválido', text: 'El NICO solo puede contener números', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('nico').focus();
|
||||
return;
|
||||
}
|
||||
if (!numero_parte) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa el número de parte.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('numero_parte').focus();
|
||||
}
|
||||
|
||||
// Función helper para hacer focus en elementos con Choices.js
|
||||
function focusChoicesElement(selectId) {
|
||||
const choicesContainer = document.querySelector(`#${selectId}`).parentElement.querySelector('.choices');
|
||||
if (choicesContainer) {
|
||||
choicesContainer.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// En tu validación del submit:
|
||||
if (!pais_o_d) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor selecciona el país de origen.', confirmButtonColor: '#dc3545' });
|
||||
focusChoicesElement('pais_origen_destino');
|
||||
return;
|
||||
}
|
||||
if (!pais_c_v) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor selecciona el país de destino.', confirmButtonColor: '#dc3545' });
|
||||
focusChoicesElement('pais_comprador_vendedor');
|
||||
return;
|
||||
}
|
||||
if (!unidad_medida) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor selecciona la unidad de medida.', confirmButtonColor: '#dc3545' });
|
||||
focusChoicesElement('umc_id');
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener todos los inputs y selects para validación
|
||||
const inputs = document.querySelectorAll('#altaProductoFrecuenteForm input[required], #altaProductoFrecuenteForm select[required]');
|
||||
|
||||
let isValid = true;
|
||||
inputs.forEach(input => {
|
||||
if (!input.checkValidity()) {
|
||||
input.classList.add('shake');
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
// Si pasa la validación, enviamos el formulario manualmente
|
||||
this.submit();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
function fetchJsonOrThrow(url) {
|
||||
return fetch(url)
|
||||
@@ -368,7 +455,7 @@
|
||||
|
||||
// Script para manejar las animaciones de validación
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const inputs = document.querySelectorAll('input.form-control, select.form_select');
|
||||
const inputs = document.querySelectorAll('input.form-control, select.form_select');
|
||||
const selects = document.querySelectorAll('select.form_select');
|
||||
|
||||
inputs.forEach(input => {
|
||||
@@ -412,19 +499,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validación del formulario
|
||||
document.getElementById('altaProductoFrecuenteForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
inputs.forEach(input => {
|
||||
if (!input.checkValidity()) {
|
||||
input.classList.add('shake');
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
<!-- views/productos_frecuentes/editar.php -->
|
||||
<?php
|
||||
include __DIR__ . '/../partials/sidebar_importador.php';
|
||||
|
||||
$vinculaciones = [
|
||||
'0' => 'No existe',
|
||||
'1' => 'Existe, no afecta',
|
||||
'2' => 'Existe y afecta'
|
||||
];
|
||||
$criterios = [
|
||||
'A' => 'Criterio A',
|
||||
'B' => 'Criterio B',
|
||||
'C' => 'Criterio C',
|
||||
'D' => 'Criterio D',
|
||||
'E' => 'Criterio E',
|
||||
'F' => 'Criterio F'
|
||||
];
|
||||
// Opciones de Vinculación y Criterio Preferencia
|
||||
$vinculaciones = [ '0' => 'No existe', '1' => 'Existe, no afecta', '2' => 'Existe y afecta' ];
|
||||
$criterios = [ 'A' => 'Criterio A', 'B' => 'Criterio B', 'C' => 'Criterio C', 'D' => 'Criterio D', 'E' => 'Criterio E', 'F' => 'Criterio F' ];
|
||||
// Proveedores, unidades y países se cargan por AJAX
|
||||
// $producto proviene del controlador
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
@@ -41,9 +32,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); }
|
||||
100% { transform: scale(1); }
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
@@ -63,6 +53,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); } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -82,65 +117,74 @@
|
||||
<div class="card p-4 shadow-sm bg-white card-hover position-relative h-auto">
|
||||
<form id="editarProductoFrecuenteForm" action="/IMPORTADORES/productos_frecuentes/actualizar" method="POST">
|
||||
<input type="hidden" name="id_producto_frecuente" value="<?= htmlspecialchars($producto['id_producto_frecuente']) ?>">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label for="sinonimo" class="form-label">Sinónimo *</label>
|
||||
<div class="row g-3 form-group-animated">
|
||||
<div class="col-md-6 form-group-animated">
|
||||
<label for="sinonimo" class="form-label">Sinónimo</label>
|
||||
<input type="text" id="sinonimo" name="sinonimo" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['sinonimo']) ?>" required>
|
||||
value="<?= htmlspecialchars($producto['sinonimo']) ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="fraccion" class="form-label">Fracción *</label>
|
||||
<input type="text" id="fraccion" name="fraccion" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['fraccion']) ?>" required>
|
||||
<div class="col-md-4 form-group-animated">
|
||||
<label for="fraccion" class="form-label">Fracción</label>
|
||||
<input type="text" id="fraccion" name="fraccion" class="form-control" maxlegth="8"
|
||||
value="<?= htmlspecialchars($producto['fraccion']) ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="nico" class="form-label">NICO *</label>
|
||||
<input type="text" id="nico" name="nico" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['nico']) ?>" required>
|
||||
<div class="col-md-2 form-group-animated">
|
||||
<label for="nico" class="form-label">NICO</label>
|
||||
<input type="text" id="nico" name="nico" class="form-control" maxlength="2"
|
||||
value="<?= htmlspecialchars($producto['nico']) ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-5 form-group-animated">
|
||||
<label for="numero_parte" class="form-label">Número de Parte</label>
|
||||
<input type="text" id="numero_parte" name="numero_parte" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['numero_parte']) ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="proveedor" class="form-label">Proveedor *</label>
|
||||
<select id="proveedor" name="proveedor" class="form-select searchable" required>
|
||||
<div class="col-md-3"></div>
|
||||
<div class="col-md-4">
|
||||
<label for="proveedor" class="form-label">Proveedor</label>
|
||||
<select id="proveedor" name="proveedor" class="form-select searchable">
|
||||
<option value="">Cargando proveedores…</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="col-12 form-group-animated">
|
||||
<label for="descripcion" class="form-label">Descripción</label>
|
||||
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"><?= htmlspecialchars($producto['descripcion']) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label for="pais_origen_destino" class="form-label">País Origen/Destino</label>
|
||||
<select id="pais_origen_destino" name="pais_origen_destino" class="form-select searchable" required>
|
||||
<select id="pais_origen_destino" name="pais_origen_destino" class="form-select searchable">
|
||||
<option value="">Cargando países…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label for="pais_comprador_vendedor" class="form-label">País Comprador/Vendedor</label>
|
||||
<select id="pais_comprador_vendedor" name="pais_comprador_vendedor" class="form-select searchable" required>
|
||||
<select id="pais_comprador_vendedor" name="pais_comprador_vendedor" class="form-select searchable">
|
||||
<option value="">Cargando países…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<label for="uso_mercancia" class="form-label">Uso de la Mercancía</label>
|
||||
<input type="text" id="uso_mercancia" name="uso_mercancia" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['uso_mercancia']) ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="umc_id" class="form-label">Unidad de Medida *</label>
|
||||
<select id="umc_id" name="umc_id" class="form-select searchable" required>
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<label for="estado_mercancia" class="form-label">Estado de la Mercancía</label>
|
||||
<input type="text" id="estado_mercancia" name="estado_mercancia" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['estado_mercancia']) ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="umc_id" class="form-label">Unidad de Medida</label>
|
||||
<select id="umc_id" name="umc_id" class="form-select searchable">
|
||||
<option value="">Cargando unidades…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="vinculacion" class="form-label">Vinculación</label>
|
||||
<select id="vinculacion" name="vinculacion" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -151,7 +195,8 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="preferencia" class="form-label">Preferencia</label>
|
||||
<select id="preferencia" name="preferencia" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -161,7 +206,7 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-3">
|
||||
<label for="criterio_preferencia" class="form-label">Criterio Preferencia</label>
|
||||
<select id="criterio_preferencia" name="criterio_preferencia" class="form-select searchable" disabled>
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -172,20 +217,26 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
||||
<div class="col-md-5 form-group-animated">
|
||||
<label for="tipo_mercancia" class="form-label">Tipo de Mercancía</label>
|
||||
<input type="text" id="tipo_mercancia" name="tipo_mercancia" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['tipo_mercancia']) ?>">
|
||||
</div>
|
||||
<div class="col-md-2"></div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="certificado_origen" name="certificado_origen" value="1"
|
||||
<?= $producto['certificado_origen']?'checked':'' ?>>
|
||||
<label class="form-check-label" for="certificado_origen">Certificado de Origen</label>
|
||||
</div>
|
||||
<div class="col-md-5 form-group-animated">
|
||||
<label for="certificado_origen" class="form-label">Certificado de Origen</label>
|
||||
<input type="text" id="certificado_origen" name="certificado_origen" class="form-control"
|
||||
value="<?= htmlspecialchars($producto['certificado_origen']) ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
|
||||
<div class="col-12 form-group-animated">
|
||||
<label for="observaciones" class="form-label">Observaciones</label>
|
||||
<textarea id="observaciones" name="observaciones" class="form-control" rows="2"><?= htmlspecialchars($producto['observaciones']) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 form-group-animated">
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="documento_en_original" name="documento_en_original" value="1"
|
||||
<?= $producto['documento_en_original']?'checked':'' ?>>
|
||||
@@ -194,7 +245,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<div class="col-12 mt-4 text-end form-group-animated">
|
||||
<button type="submit" class="btn btn-primary mt-auto w-auto btn-animated">Actualizar</button>
|
||||
<a href="/IMPORTADORES/productos_frecuentes" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
|
||||
</div>
|
||||
@@ -206,118 +257,200 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Helper para fetch + validación JSON
|
||||
function fetchJsonOrThrow(url) {
|
||||
return fetch(url)
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status} en ${url}`);
|
||||
return resp.text();
|
||||
})
|
||||
.then(txt => {
|
||||
try {
|
||||
return JSON.parse(txt);
|
||||
} catch (e) {
|
||||
console.error(`Respuesta no-JSON en ${url}:`, txt);
|
||||
throw e;
|
||||
document.getElementById('editarProductoFrecuenteForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const sinonimo = document.getElementById('sinonimo').value.trim();
|
||||
const fraccion = document.getElementById('fraccion').value.trim();
|
||||
const nico = document.getElementById('nico').value.trim();
|
||||
const numero_parte = document.getElementById('numero_parte').value.trim();
|
||||
const pais_o_d = document.getElementById('pais_origen_destino').value;
|
||||
const pais_c_v = document.getElementById('pais_comprador_vendedor').value;
|
||||
const unidad_medida = document.getElementById('umc_id').value;
|
||||
|
||||
const soloNumRegex = /^[0-9]+$/;
|
||||
|
||||
if (!sinonimo) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa el sinónimo.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('sinonimo').focus();
|
||||
}
|
||||
if (!fraccion) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa la fracción.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('fraccion').focus();
|
||||
}
|
||||
if (!soloNumRegex.test(fraccion)) {
|
||||
Swal.fire({ icon: 'error', title: 'Fracción inválida', text: 'La fracción solo puede contener números', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('fraccion').focus();
|
||||
return;
|
||||
}
|
||||
if (!nico) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa el NICO.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('nico').focus();
|
||||
}
|
||||
if (!soloNumRegex.test(nico)) {
|
||||
Swal.fire({ icon: 'error', title: 'NICO inválido', text: 'El NICO solo puede contener números', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('nico').focus();
|
||||
return;
|
||||
}
|
||||
if (!numero_parte) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor ingresa el número de parte.', confirmButtonColor: '#dc3545' });
|
||||
return document.getElementById('numero_parte').focus();
|
||||
}
|
||||
|
||||
// Función helper para hacer focus en elementos con Choices.js
|
||||
function focusChoicesElement(selectId) {
|
||||
const choicesContainer = document.querySelector(`#${selectId}`).parentElement.querySelector('.choices');
|
||||
if (choicesContainer) {
|
||||
choicesContainer.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// En tu validación del submit:
|
||||
if (!pais_o_d) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor selecciona el país de origen.', confirmButtonColor: '#dc3545' });
|
||||
focusChoicesElement('pais_origen_destino');
|
||||
return;
|
||||
}
|
||||
if (!pais_c_v) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor selecciona el país de destino.', confirmButtonColor: '#dc3545' });
|
||||
focusChoicesElement('pais_comprador_vendedor');
|
||||
return;
|
||||
}
|
||||
if (!unidad_medida) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Por favor selecciona la unidad de medida.', confirmButtonColor: '#dc3545' });
|
||||
focusChoicesElement('umc_id');
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener todos los inputs y selects para validación
|
||||
const inputs = document.querySelectorAll('#altaProductoFrecuenteForm input[required], #altaProductoFrecuenteForm select[required]');
|
||||
|
||||
let isValid = true;
|
||||
inputs.forEach(input => {
|
||||
if (!input.checkValidity()) {
|
||||
input.classList.add('shake');
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Referencias a los <select>
|
||||
const selProv = document.getElementById('proveedor');
|
||||
const selUmc = document.getElementById('umc_id');
|
||||
const selPor = document.getElementById('pais_origen_destino');
|
||||
const selPcv = document.getElementById('pais_comprador_vendedor');
|
||||
const prefEl = document.getElementById('preferencia');
|
||||
const critEl = document.getElementById('criterio_preferencia');
|
||||
|
||||
// Valores previamente guardados (inyectados por PHP)
|
||||
const savedProv = <?= json_encode($producto['proveedor']) ?>;
|
||||
const savedUmc = <?= json_encode($producto['umc_id']) ?>;
|
||||
const savedPor = <?= json_encode($producto['pais_origen_destino']) ?>;
|
||||
const savedPcv = <?= json_encode($producto['pais_comprador_vendedor']) ?>;
|
||||
const savedPref = <?= json_encode($producto['preferencia']) ?>;
|
||||
const savedCrit = <?= json_encode($producto['criterio_preferencia']) ?>;
|
||||
|
||||
Promise.all([
|
||||
fetchJsonOrThrow('/IMPORTADORES/solicitud_importacion/ajax_proveedores'),
|
||||
fetchJsonOrThrow('/IMPORTADORES/productos_frecuentes/ajax_unidades'),
|
||||
fetchJsonOrThrow('/IMPORTADORES/productos_frecuentes/ajax_paises')
|
||||
])
|
||||
.then(([provData, umcData, paisData]) => {
|
||||
// Proveedores
|
||||
selProv.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
|
||||
provData.results.forEach(it => {
|
||||
const o = document.createElement('option');
|
||||
o.value = it.id;
|
||||
o.textContent = it.text;
|
||||
if (it.id == savedProv) o.selected = true;
|
||||
selProv.appendChild(o);
|
||||
});
|
||||
|
||||
// Unidades de Medida
|
||||
selUmc.innerHTML = '<option value="">-- Selecciona UMC --</option>';
|
||||
umcData.results.forEach(it => {
|
||||
const o = document.createElement('option');
|
||||
o.value = it.id;
|
||||
o.textContent = it.text;
|
||||
if (it.id == savedUmc) o.selected = true;
|
||||
selUmc.appendChild(o);
|
||||
});
|
||||
|
||||
// Países de Origen/Destino y Comprador/Vendedor
|
||||
selPor.innerHTML = '<option value="">-- Selecciona País --</option>';
|
||||
selPcv.innerHTML = '<option value="">-- Selecciona País --</option>';
|
||||
paisData.results.forEach(it => {
|
||||
[selPor, selPcv].forEach(sel => {
|
||||
const o = document.createElement('option');
|
||||
o.value = it.id;
|
||||
o.textContent = it.text;
|
||||
if (sel === selPor && it.id == savedPor) o.selected = true;
|
||||
if (sel === selPcv && it.id == savedPcv) o.selected = true;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
});
|
||||
|
||||
// Inicializar Choices.js en todos los .searchable
|
||||
document.querySelectorAll('.searchable').forEach(el => {
|
||||
if (el._choices) el._choices.destroy();
|
||||
el._choices = new Choices(el, {
|
||||
searchEnabled: true,
|
||||
itemSelectText: '',
|
||||
shouldSort: false
|
||||
});
|
||||
});
|
||||
|
||||
// Restaurar y controlar Preferencia → Criterio
|
||||
prefEl.value = savedPref || '';
|
||||
function updateCriterio() {
|
||||
if (prefEl.value === 'PROSEC') {
|
||||
critEl.disabled = false;
|
||||
critEl._choices.enable();
|
||||
critEl.value = savedCrit || '';
|
||||
critEl._choices.setChoiceByValue(savedCrit);
|
||||
} else {
|
||||
critEl._choices.removeActiveItems();
|
||||
critEl._choices.disable();
|
||||
critEl.disabled = true;
|
||||
if (isValid) {
|
||||
// Si pasa la validación, enviamos el formulario manualmente
|
||||
this.submit();
|
||||
}
|
||||
}
|
||||
// Inicial y evento
|
||||
updateCriterio();
|
||||
prefEl.addEventListener('change', updateCriterio);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error en AJAX:', err);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error cargando datos',
|
||||
text: err.message
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Helper para fetch + validación JSON
|
||||
function fetchJsonOrThrow(url) {
|
||||
return fetch(url)
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status} en ${url}`);
|
||||
return resp.text();
|
||||
})
|
||||
.then(txt => {
|
||||
try {
|
||||
return JSON.parse(txt);
|
||||
} catch (e) {
|
||||
console.error(`Respuesta no-JSON en ${url}:`, txt);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Referencias a los <select>
|
||||
const selProv = document.getElementById('proveedor');
|
||||
const selUmc = document.getElementById('umc_id');
|
||||
const selPor = document.getElementById('pais_origen_destino');
|
||||
const selPcv = document.getElementById('pais_comprador_vendedor');
|
||||
const prefEl = document.getElementById('preferencia');
|
||||
const critEl = document.getElementById('criterio_preferencia');
|
||||
|
||||
// Valores previamente guardados (inyectados por PHP)
|
||||
const savedProv = <?= json_encode($producto['proveedor']) ?>;
|
||||
const savedUmc = <?= json_encode($producto['umc_id']) ?>;
|
||||
const savedPor = <?= json_encode($producto['pais_origen_destino']) ?>;
|
||||
const savedPcv = <?= json_encode($producto['pais_comprador_vendedor']) ?>;
|
||||
const savedPref = <?= json_encode($producto['preferencia']) ?>;
|
||||
const savedCrit = <?= json_encode($producto['criterio_preferencia']) ?>;
|
||||
|
||||
Promise.all([
|
||||
fetchJsonOrThrow('/IMPORTADORES/solicitud_importacion/ajax_proveedores'),
|
||||
fetchJsonOrThrow('/IMPORTADORES/productos_frecuentes/ajax_unidades'),
|
||||
fetchJsonOrThrow('/IMPORTADORES/productos_frecuentes/ajax_paises')
|
||||
])
|
||||
.then(([provData, umcData, paisData]) => {
|
||||
// Proveedores
|
||||
selProv.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
|
||||
provData.results.forEach(it => {
|
||||
const o = document.createElement('option');
|
||||
o.value = it.id;
|
||||
o.textContent = it.text;
|
||||
if (it.id == savedProv) o.selected = true;
|
||||
selProv.appendChild(o);
|
||||
});
|
||||
|
||||
// Unidades de Medida
|
||||
selUmc.innerHTML = '<option value="">-- Selecciona UMC --</option>';
|
||||
umcData.results.forEach(it => {
|
||||
const o = document.createElement('option');
|
||||
o.value = it.id;
|
||||
o.textContent = it.text;
|
||||
if (it.id == savedUmc) o.selected = true;
|
||||
selUmc.appendChild(o);
|
||||
});
|
||||
|
||||
// Países de Origen/Destino y Comprador/Vendedor
|
||||
selPor.innerHTML = '<option value="">-- Selecciona País --</option>';
|
||||
selPcv.innerHTML = '<option value="">-- Selecciona País --</option>';
|
||||
paisData.results.forEach(it => {
|
||||
[selPor, selPcv].forEach(sel => {
|
||||
const o = document.createElement('option');
|
||||
o.value = it.id;
|
||||
o.textContent = it.text;
|
||||
if (sel === selPor && it.id == savedPor) o.selected = true;
|
||||
if (sel === selPcv && it.id == savedPcv) o.selected = true;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
});
|
||||
|
||||
// Inicializar Choices.js en todos los .searchable
|
||||
document.querySelectorAll('.searchable').forEach(el => {
|
||||
if (el._choices) el._choices.destroy();
|
||||
el._choices = new Choices(el, {
|
||||
searchEnabled: true,
|
||||
itemSelectText: '',
|
||||
shouldSort: false
|
||||
});
|
||||
});
|
||||
|
||||
// Restaurar y controlar Preferencia → Criterio
|
||||
prefEl.value = savedPref || '';
|
||||
function updateCriterio() {
|
||||
if (prefEl.value === 'PROSEC') {
|
||||
critEl.disabled = false;
|
||||
critEl._choices.enable();
|
||||
critEl.value = savedCrit || '';
|
||||
critEl._choices.setChoiceByValue(savedCrit);
|
||||
} else {
|
||||
critEl._choices.removeActiveItems();
|
||||
critEl._choices.disable();
|
||||
critEl.disabled = true;
|
||||
}
|
||||
}
|
||||
// Inicial y evento
|
||||
updateCriterio();
|
||||
prefEl.addEventListener('change', updateCriterio);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error en AJAX:', err);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error cargando datos',
|
||||
text: err.message
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -57,26 +57,19 @@
|
||||
|
||||
<?php if (!empty($_SESSION['flash_error'])): ?>
|
||||
<script>
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: '<?= addslashes($_SESSION['flash_error']) ?>'
|
||||
});
|
||||
Swal.fire({ icon: 'error', title: 'Error', text: '<?= addslashes($_SESSION['flash_error']) ?>' });
|
||||
</script>
|
||||
<?php unset($_SESSION['flash_error']); endif; ?>
|
||||
|
||||
<?php if (!empty($_SESSION['flash_success'])): ?>
|
||||
<script>
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: '¡Listo!',
|
||||
text: '<?= addslashes($_SESSION['flash_success']) ?>'
|
||||
});
|
||||
Swal.fire({ icon: 'success', title: '¡Listo!', text: '<?= addslashes($_SESSION['flash_success']) ?>' });
|
||||
</script>
|
||||
<?php unset($_SESSION['flash_success']); endif; ?>
|
||||
|
||||
<div class="content">
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">📋 Productos Frecuentes</h4>
|
||||
<a href="/IMPORTADORES/productos_frecuentes/alta" class="btn btn-success mb-3 mt-auto w-auto btn-animated">➕ Nuevo Producto</a>
|
||||
<div class="card p-4 shadow-sm card-hover position-relative h-auto">
|
||||
<div class="table-responsive">
|
||||
<table id="tablaProductosFrecuentes" class="table table-striped table-bordered">
|
||||
@@ -99,50 +92,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$conn = getConnection();
|
||||
$sql = "
|
||||
SELECT
|
||||
pf.id_producto_frecuente,
|
||||
pf.sinonimo,
|
||||
pf.fraccion,
|
||||
pf.nico,
|
||||
pf.numero_parte,
|
||||
pf.proveedor AS proveedor_guardado,
|
||||
u.descripcion AS unidad_de_medida,
|
||||
por.nombre AS pais_origen_destino,
|
||||
pc.nombre AS pais_comprador_vendedor,
|
||||
pf.uso_mercancia,
|
||||
pf.estado_mercancia,
|
||||
pf.preferencia,
|
||||
pf.frecuencia_uso
|
||||
FROM dbo.productos_frecuentes pf
|
||||
LEFT JOIN dbo.unidades_medida_apendice7 u
|
||||
ON pf.umc_id = u.id
|
||||
LEFT JOIN dbo.paises por
|
||||
ON pf.pais_origen_destino = por.id_pais
|
||||
LEFT JOIN dbo.paises pc
|
||||
ON pf.pais_comprador_vendedor = pc.id_pais
|
||||
WHERE pf.id_importador = ?
|
||||
ORDER BY pf.fecha_alta DESC
|
||||
";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$_SESSION['usuario_id']]);
|
||||
|
||||
if ($stmt === false) {
|
||||
echo '<pre>', print_r(sqlsrv_errors(), true), '</pre>';
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)):
|
||||
?>
|
||||
<?php foreach ($productos as $row): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($row['id_producto_frecuente']) ?></td>
|
||||
<td><?= htmlspecialchars($row['sinonimo']) ?></td>
|
||||
<td><?= htmlspecialchars($row['fraccion']) ?></td>
|
||||
<td><?= htmlspecialchars($row['nico']) ?></td>
|
||||
<td><?= htmlspecialchars($row['numero_parte']) ?></td>
|
||||
<td><?= htmlspecialchars($row['proveedor_guardado']) ?></td>
|
||||
<td><?= htmlspecialchars($row['nombre_proveedor']) ?></td>
|
||||
<td><?= htmlspecialchars($row['unidad_de_medida']) ?></td>
|
||||
<td><?= htmlspecialchars($row['pais_origen_destino']) ?></td>
|
||||
<td><?= htmlspecialchars($row['pais_comprador_vendedor']) ?></td>
|
||||
@@ -151,16 +108,12 @@
|
||||
<td><?= htmlspecialchars($row['preferencia']) ?></td>
|
||||
<td><?= htmlspecialchars($row['frecuencia_uso']) ?></td>
|
||||
<td class="text-center">
|
||||
<a href="/IMPORTADORES/productos_frecuentes/editar?id=<?= $row['id_producto_frecuente'] ?>"
|
||||
class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
|
||||
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated"
|
||||
onclick="confirmDelete(<?= $row['id_producto_frecuente'] ?>)">
|
||||
🗑️
|
||||
</button>
|
||||
<a href="/IMPORTADORES/productos_frecuentes/editar?id=<?= $row['id_producto_frecuente'] ?>" class="btn btn-sm btn-primary mt-auto w-auto btn-animated">✏️</a>
|
||||
<button class="btn btn-sm btn-danger mt-auto w-auto btn-animated" onclick="confirmDelete(<?= $row['id_producto_frecuente'] ?>)">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endwhile; ?>
|
||||
</tbody>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,6 +146,11 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
<?php if (isset($_GET['deleted']) && $_GET['deleted']==='ok'): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Producto eliminado', text: 'Ya no aparecerá en tu lista.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,13 +8,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Choices.js CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||
<!-- Sweet Alert 2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css para animaciones adicionales -->
|
||||
@@ -59,80 +59,6 @@
|
||||
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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -145,19 +71,19 @@
|
||||
|
||||
<!-- Datos principales -->
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3 form-group-animated">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="numero_factura" class="form-label">Número de Factura</label>
|
||||
<input id="numero_factura" name="numero_factura" type="text" class="form-control" required
|
||||
value="<?= htmlspecialchars($factura['numero_factura']) ?>">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3 form-group-animated">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="fecha_factura" class="form-label">Fecha de Factura</label>
|
||||
<input id="fecha_factura" name="fecha_factura" type="date" class="form-control" required
|
||||
value="<?= htmlspecialchars($factura['fecha_factura']) ?>">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="proveedor_clave" class="form-label">Proveedor</label>
|
||||
<select id="proveedor_clave" name="proveedor_clave" class="form-select searchable">
|
||||
<label for="proveedor_id" class="form-label">Proveedor</label>
|
||||
<select id="proveedor_id" name="proveedor_clave" class="form-select searchable">
|
||||
<option value="">Cargando proveedores...</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -224,7 +150,7 @@
|
||||
|
||||
<!-- Valor y Vinculación -->
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3 form-group-animated">
|
||||
<div class="col-md-8 mb-3">
|
||||
<label for="valor_factura" class="form-label">Valor Factura</label>
|
||||
<input id="valor_factura" name="valor_factura" type="number" step="0.01" min="0" class="form-control valor-total" required
|
||||
value="<?= htmlspecialchars($factura['valor_factura']) ?>">
|
||||
@@ -267,7 +193,7 @@
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
|
||||
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control no-animation no-border-style" accept="image/*">
|
||||
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -293,9 +219,9 @@
|
||||
<tbody>
|
||||
<?php if (!empty($partidas)): foreach($partidas as $i=>$p): ?>
|
||||
<tr>
|
||||
<td><input name="partidas[<?= $i ?>][descripcion]" class="form-control form-group-animated" value="<?= htmlspecialchars($p['descripcion']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][descripcion]" class="form-control" value="<?= htmlspecialchars($p['descripcion']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
|
||||
<td>
|
||||
<select name="partidas[<?= $i ?>][unidad_comercial_id]" class="form-select searchable">
|
||||
<option value="">-- Unidad --</option>
|
||||
@@ -306,8 +232,8 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td><input name="partidas[<?= $i ?>][valor_factura]" type="number" step="0.01" min="0" class="form-control valor-partida form-group-animated" value="<?= htmlspecialchars($p['valor_factura']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][peso_bruto]" type="number" step="0.0001" min="0" class="form-control form-group-animated" value="<?= htmlspecialchars($p['peso_bruto']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][valor_factura]" type="number" step="0.01" min="0" class="form-control valor-partida" value="<?= htmlspecialchars($p['valor_factura']) ?>"></td>
|
||||
<td><input name="partidas[<?= $i ?>][peso_bruto]" type="number" step="0.0001" min="0" class="form-control" value="<?= htmlspecialchars($p['peso_bruto']) ?>"></td>
|
||||
<td>
|
||||
<select name="partidas[<?= $i ?>][tasa_preferencial]" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -316,15 +242,15 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td class="hide"><input name="partidas[<?= $i ?>][precio_unitario]" type="number" class="form-control form-group-animated" value="<?= htmlspecialchars($p['precio_unitario'] ?? '') ?>"></td>
|
||||
<td class="hide"><input name="partidas[<?= $i ?>][oma_factura]" class="form-control form-group-animated" value="<?= htmlspecialchars($p['oma_factura'] ?? '') ?>"></td>
|
||||
<td class="hide"><input name="partidas[<?= $i ?>][precio_unitario]" type="number" class="form-control" value="<?= htmlspecialchars($p['precio_unitario'] ?? '') ?>"></td>
|
||||
<td class="hide"><input name="partidas[<?= $i ?>][oma_factura]" class="form-control" value="<?= htmlspecialchars($p['oma_factura'] ?? '') ?>"></td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td>
|
||||
</tr>
|
||||
<?php endforeach; else: ?>
|
||||
<tr>
|
||||
<td><input name="partidas[0][descripcion]" class="form-control form-group-animated"></td>
|
||||
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
|
||||
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
|
||||
<td><input name="partidas[0][descripcion]" class="form-control"></td>
|
||||
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control" min="0"></td>
|
||||
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control" min="0"></td>
|
||||
<td>
|
||||
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable">
|
||||
<option value="">-- Unidad --</option>
|
||||
@@ -333,8 +259,8 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida form-group-animated" min="0"></td>
|
||||
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control form-group-animated" min="0"></td>
|
||||
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida" min="0"></td>
|
||||
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control" min="0"></td>
|
||||
<td>
|
||||
<select name="partidas[0][tasa_preferencial]" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
@@ -350,79 +276,75 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="form-group-animated">
|
||||
<button type="button" class="btn btn-secondary btn-sm mb-3 mt-auto w-auto btn-animated" id="add-partida">➕ Agregar partida</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary btn-sm mb-3 mt-auto w-auto btn-animated" id="add-partida">➕ Agregar partida</button>
|
||||
|
||||
<!-- Activo -->
|
||||
<div class="form-check mb-4 form-group-animated">
|
||||
<div class="form-check mb-4">
|
||||
<input id="status" value=1 name="status" type="hidden" <?= $factura['status']==1?'checked':'' ?>>
|
||||
<label for="status" class="hide form-check-label">Activo</label>
|
||||
</div>
|
||||
|
||||
<!-- Botones finales -->
|
||||
<div class="col-12 text-end mt-4 form-group-animated"></div>
|
||||
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
|
||||
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
|
||||
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Choices.js JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<script>
|
||||
// ✅ 1. INICIALIZACIÓN PRINCIPAL
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Primero cargar proveedores, después inicializar otros selects
|
||||
cargarProveedores().then(() => {
|
||||
// Inicializar Choices.js en todos los selects searchable DESPUÉS de cargar proveedores
|
||||
const proveedorActual = '<?= htmlspecialchars($proveedor_clave_actual ?? '') ?>';
|
||||
console.log('📦 Proveedor actual:', proveedorActual);
|
||||
|
||||
cargarProveedores(proveedorActual).then(() => {
|
||||
document.querySelectorAll('.searchable').forEach(el => {
|
||||
new Choices(el, {
|
||||
// Destruir instancia previa si existe
|
||||
if (el.choicesInstance) el.choicesInstance.destroy();
|
||||
|
||||
const instance = new Choices(el, {
|
||||
searchEnabled: true,
|
||||
itemSelectText: '',
|
||||
shouldSort: false
|
||||
});
|
||||
|
||||
// Guardar instancia
|
||||
el.choicesInstance = instance;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 2. FUNCIÓN PARA CARGAR PROVEEDORES (CON PROMESA)
|
||||
function cargarProveedores() {
|
||||
const proveedorEl = document.getElementById('proveedor_clave');
|
||||
// Obtenemos la cadena con la ID del proveedor guardado
|
||||
const proveedorActual = '<?= htmlspecialchars($proveedor_clave_actual ?? '') ?>';
|
||||
|
||||
function cargarProveedores(proveedorActual) {
|
||||
const proveedorEl = document.getElementById('proveedor_id');
|
||||
return fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.json();
|
||||
})
|
||||
.then(json => {
|
||||
// Limpiar el select
|
||||
proveedorEl.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
|
||||
|
||||
// Agregar todas las opciones
|
||||
if (json.results && json.results.length > 0) {
|
||||
json.results.forEach(item => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = item.id;
|
||||
opt.textContent = item.text;
|
||||
json.results.forEach(item => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = item.id;
|
||||
opt.textContent = item.text;
|
||||
|
||||
// Comparar forzando a cadena para que coincida con proveedorActual
|
||||
if (String(item.id) === proveedorActual && proveedorActual !== '') {
|
||||
opt.selected = true;
|
||||
}
|
||||
if (String(item.id) === proveedorActual) {
|
||||
opt.selected = true;
|
||||
}
|
||||
|
||||
proveedorEl.appendChild(opt);
|
||||
});
|
||||
}
|
||||
proveedorEl.appendChild(opt);
|
||||
});
|
||||
|
||||
// Forzar manualmente el value
|
||||
proveedorEl.value = proveedorActual;
|
||||
console.log('✅ Proveedores cargados. Proveedor actual:', proveedorActual);
|
||||
console.log('🧪 Select value actual (después de asignar):', proveedorEl.value);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ Error cargando proveedores:', err);
|
||||
@@ -433,8 +355,8 @@
|
||||
// ✅ 3. AGREGAR PARTIDAS
|
||||
document.getElementById('add-partida').addEventListener('click', () => {
|
||||
const tbody = document.querySelector('#tabla-partidas tbody');
|
||||
const idx = tbody.querySelectorAll('tr').length;
|
||||
const row = document.createElement('tr');
|
||||
const idx = tbody.querySelectorAll('tr').length;
|
||||
const row = document.createElement('tr');
|
||||
|
||||
row.innerHTML = `
|
||||
<td><input name="partidas[${idx}][descripcion]" class="form-control"></td>
|
||||
@@ -453,7 +375,11 @@
|
||||
<td>
|
||||
<select name="partidas[${idx}][tasa_preferencial]" class="form-select searchable">
|
||||
<option value="">-- Selecciona --</option>
|
||||
<option>General</option><option>TLC</option><option>PROSEC</option><option>ALADI</option><option>COMERCIALIZADORA</option>
|
||||
<option>General</option>
|
||||
<option>TLC</option>
|
||||
<option>PROSEC</option>
|
||||
<option>ALADI</option>
|
||||
<option>COMERCIALIZADORA</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="hide"><input name="partidas[${idx}][precio_unitario]" type="number" class="form-control"></td>
|
||||
@@ -716,67 +642,6 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 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('solicitudForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
inputs.forEach(input => {
|
||||
if (!input.checkValidity()) {
|
||||
input.classList.add('shake');
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -333,9 +333,6 @@
|
||||
|
||||
// Aquí puedes enviar el formulario real
|
||||
form.submit();
|
||||
} else {
|
||||
// Mostrar mensaje de error
|
||||
showNotification('Por favor, complete todos los campos obligatorios correctamente.', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user