Autosave: 20260725-171839
This commit is contained in:
parent
8633846941
commit
7a6edd7faf
@ -1382,7 +1382,7 @@ require_once 'layout_header.php';
|
||||
<?php $rowHtml = ob_get_clean(); echo $rowHtml; ?>
|
||||
|
||||
<?php ob_start(); ?>
|
||||
<div class="modal fade" id="<?php echo htmlspecialchars($modalId); ?>" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="<?php echo htmlspecialchars($modalId); ?>" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header align-items-start gap-2">
|
||||
@ -1601,7 +1601,7 @@ require_once 'layout_header.php';
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">SEDE / ID</label>
|
||||
<input type="text" class="form-control" id="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($numeroCuentaSedeIdSeleccionado); ?>" placeholder="Ej.: SJL / 1542">
|
||||
<div class="form-text">Solo aparece en <strong>SE ENVIO NUMERO DE CUENTA</strong>.</div>
|
||||
<div class="form-text">Se muestra en <strong>SE ENVIO NUMERO DE CUENTA</strong> y se mantiene en <strong>CONFIRMADO ENVIO</strong>.</div>
|
||||
</div>
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">N° DNI</label>
|
||||
@ -1783,7 +1783,6 @@ require_once 'layout_header.php';
|
||||
<?php if (in_array($_SESSION['user_role'] ?? '', ['Administrador', 'admin'], true)): ?>
|
||||
<button type="button" class="btn btn-outline-danger" onclick="eliminarPedido('<?php echo htmlspecialchars($order['source_key']); ?>', this)">Eliminar del panel</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
<button type="button" class="btn btn-primary" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this)">Guardar gestión</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -2061,7 +2060,7 @@ function toggleAgendaFields(sourceKey) {
|
||||
const deliveryInput = document.getElementById('fecha-entrega-' + sourceKey);
|
||||
const needsDeliveryDate = estado === 'CONFIRMADO CONTRAENTREGA';
|
||||
const needsShippingDetails = estado === 'CONFIRMADO ENVIO';
|
||||
const needsAccountNumberDetails = estado === 'SE ENVIO NUMERO DE CUENTA';
|
||||
const needsAccountNumberDetails = ['SE ENVIO NUMERO DE CUENTA', 'CONFIRMADO ENVIO'].includes(estado);
|
||||
const needsNextCall = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'].includes(estado);
|
||||
|
||||
if (nextCallGroup) {
|
||||
@ -2378,7 +2377,7 @@ function showAirDroidHelper(details) {
|
||||
if (modalId) {
|
||||
const modalElement = document.getElementById(modalId);
|
||||
if (modalElement && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement).show();
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement, { backdrop: 'static', keyboard: false }).show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2422,7 +2421,7 @@ function registrarLlamada(event, trigger) {
|
||||
if (modalId) {
|
||||
const modalElement = document.getElementById(modalId);
|
||||
if (modalElement && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement).show();
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement, { backdrop: 'static', keyboard: false }).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
db/migrations/087_add_resultados_to_marketing_videos.sql
Normal file
3
db/migrations/087_add_resultados_to_marketing_videos.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- Agrega la columna de resultados para Producción de Video
|
||||
ALTER TABLE marketing_videos
|
||||
ADD COLUMN IF NOT EXISTS resultados ENUM('DESCONTINUADO', 'REDIMIENTO MEDIUM', 'BUEN RENDIMIENTO') NULL AFTER estado;
|
||||
@ -0,0 +1,3 @@
|
||||
-- Amplía el ENUM de resultados para incluir REDIMIENTO MEDIUM.
|
||||
ALTER TABLE marketing_videos
|
||||
MODIFY COLUMN resultados ENUM('DESCONTINUADO', 'REDIMIENTO MEDIUM', 'BUEN RENDIMIENTO') NULL AFTER estado;
|
||||
@ -2066,7 +2066,7 @@ require_once 'layout_header.php';
|
||||
<?php $rowHtml = ob_get_clean(); echo $rowHtml; ?>
|
||||
|
||||
<?php ob_start(); ?>
|
||||
<div class="modal fade" id="<?php echo htmlspecialchars($modalId); ?>" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="<?php echo htmlspecialchars($modalId); ?>" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header align-items-start gap-2">
|
||||
@ -2285,7 +2285,7 @@ require_once 'layout_header.php';
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">SEDE / ID</label>
|
||||
<input type="text" class="form-control" id="numero_cuenta_sede_id-<?php echo htmlspecialchars($order['source_key']); ?>" value="<?php echo htmlspecialchars($numeroCuentaSedeIdSeleccionado); ?>" placeholder="Ej.: SJL / 1542">
|
||||
<div class="form-text">Solo aparece en <strong>SE ENVIO NUMERO DE CUENTA</strong>.</div>
|
||||
<div class="form-text">Se muestra en <strong>SE ENVIO NUMERO DE CUENTA</strong> y se mantiene en <strong>CONFIRMADO ENVIO</strong>.</div>
|
||||
</div>
|
||||
<div class="col-md-6 js-numero-cuenta-group <?php echo $mostrarCamposNumeroCuenta ? '' : 'd-none'; ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
<label for="numero_cuenta_dni-<?php echo htmlspecialchars($order['source_key']); ?>" class="form-label">N° DNI</label>
|
||||
@ -2467,7 +2467,6 @@ require_once 'layout_header.php';
|
||||
<?php if (in_array($_SESSION['user_role'] ?? '', ['Administrador', 'admin'], true)): ?>
|
||||
<button type="button" class="btn btn-outline-danger" onclick="eliminarPedido('<?php echo htmlspecialchars($order['source_key']); ?>', this)">Eliminar del panel</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
<button type="button" class="btn btn-primary" onclick="guardarGestion('<?php echo htmlspecialchars($order['source_key']); ?>', this)">Guardar gestión</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -2790,7 +2789,7 @@ function toggleAgendaFields(sourceKey) {
|
||||
const deliveryInput = document.getElementById('fecha-entrega-' + sourceKey);
|
||||
const needsDeliveryDate = estado === 'CONFIRMADO CONTRAENTREGA';
|
||||
const needsShippingDetails = estado === 'CONFIRMADO ENVIO';
|
||||
const needsAccountNumberDetails = estado === 'SE ENVIO NUMERO DE CUENTA';
|
||||
const needsAccountNumberDetails = ['SE ENVIO NUMERO DE CUENTA', 'CONFIRMADO ENVIO'].includes(estado);
|
||||
const needsNextCall = ['POR LLAMAR', 'DEVOLVER LLAMADA', 'OBSERVADO'].includes(estado);
|
||||
|
||||
if (nextCallGroup) {
|
||||
@ -3107,7 +3106,7 @@ function showAirDroidHelper(details) {
|
||||
if (modalId) {
|
||||
const modalElement = document.getElementById(modalId);
|
||||
if (modalElement && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement).show();
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement, { backdrop: 'static', keyboard: false }).show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3151,7 +3150,7 @@ function registrarLlamada(event, trigger) {
|
||||
if (modalId) {
|
||||
const modalElement = document.getElementById(modalId);
|
||||
if (modalElement && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement).show();
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalElement, { backdrop: 'static', keyboard: false }).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1199,7 +1199,7 @@ function cc_test_requires_shipping_details(string $estado): bool
|
||||
|
||||
function cc_test_requires_account_number_fields(string $estado): bool
|
||||
{
|
||||
return cc_test_normalize_state($estado) === 'SE ENVIO NUMERO DE CUENTA';
|
||||
return in_array(cc_test_normalize_state($estado), ['SE ENVIO NUMERO DE CUENTA', 'CONFIRMADO ENVIO'], true);
|
||||
}
|
||||
|
||||
function cc_test_pending_logistica_destination(array $order): ?string
|
||||
|
||||
@ -2,6 +2,62 @@
|
||||
// Auto-inicializa pequeños ajustes del esquema de Marketing cuando faltan columnas.
|
||||
// Así evitamos errores al abrir la pantalla o guardar datos nuevos.
|
||||
|
||||
function marketing_apply_migration_if_missing(PDO $pdo, string $columnName, string $migrationFile): void
|
||||
{
|
||||
$checkColumnStmt = $pdo->prepare(
|
||||
'SELECT COUNT(*) AS c '
|
||||
. 'FROM information_schema.columns '
|
||||
. 'WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?'
|
||||
);
|
||||
|
||||
$checkColumnStmt->execute(['marketing_videos', $columnName]);
|
||||
$row = $checkColumnStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$count = (int)($row['c'] ?? 0);
|
||||
|
||||
if ($count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_file($migrationFile)) {
|
||||
throw new RuntimeException('No se encontró la migración: ' . basename($migrationFile));
|
||||
}
|
||||
|
||||
$sql = file_get_contents($migrationFile);
|
||||
if ($sql === false) {
|
||||
throw new RuntimeException('No se pudo leer la migración: ' . basename($migrationFile));
|
||||
}
|
||||
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
|
||||
function marketing_apply_migration_if_column_type_missing(PDO $pdo, string $columnName, string $needle, string $migrationFile): void
|
||||
{
|
||||
$checkColumnStmt = $pdo->prepare(
|
||||
'SELECT COLUMN_TYPE AS column_type '
|
||||
. 'FROM information_schema.columns '
|
||||
. 'WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?'
|
||||
);
|
||||
|
||||
$checkColumnStmt->execute(['marketing_videos', $columnName]);
|
||||
$row = $checkColumnStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$columnType = (string)($row['column_type'] ?? '');
|
||||
|
||||
if ($columnType !== '' && stripos($columnType, $needle) !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_file($migrationFile)) {
|
||||
throw new RuntimeException('No se encontró la migración: ' . basename($migrationFile));
|
||||
}
|
||||
|
||||
$sql = file_get_contents($migrationFile);
|
||||
if ($sql === false) {
|
||||
throw new RuntimeException('No se pudo leer la migración: ' . basename($migrationFile));
|
||||
}
|
||||
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
|
||||
function marketing_ensure_schema(PDO $pdo): void
|
||||
{
|
||||
static $attempted = false;
|
||||
@ -10,27 +66,22 @@ function marketing_ensure_schema(PDO $pdo): void
|
||||
}
|
||||
$attempted = true;
|
||||
|
||||
$checkColumnStmt = $pdo->prepare(
|
||||
'SELECT COUNT(*) AS c '
|
||||
. 'FROM information_schema.columns '
|
||||
. 'WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?'
|
||||
marketing_apply_migration_if_missing(
|
||||
$pdo,
|
||||
'observacion',
|
||||
__DIR__ . '/../db/migrations/086_add_observacion_to_marketing_videos.sql'
|
||||
);
|
||||
|
||||
$checkColumnStmt->execute(['marketing_videos', 'observacion']);
|
||||
$row = $checkColumnStmt->fetch(PDO::FETCH_ASSOC);
|
||||
$count = (int)($row['c'] ?? 0);
|
||||
marketing_apply_migration_if_missing(
|
||||
$pdo,
|
||||
'resultados',
|
||||
__DIR__ . '/../db/migrations/087_add_resultados_to_marketing_videos.sql'
|
||||
);
|
||||
|
||||
if ($count === 0) {
|
||||
$file = __DIR__ . '/../db/migrations/086_add_observacion_to_marketing_videos.sql';
|
||||
if (!is_file($file)) {
|
||||
throw new RuntimeException('No se encontró la migración: ' . basename($file));
|
||||
}
|
||||
|
||||
$sql = file_get_contents($file);
|
||||
if ($sql === false) {
|
||||
throw new RuntimeException('No se pudo leer la migración: ' . basename($file));
|
||||
}
|
||||
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
marketing_apply_migration_if_column_type_missing(
|
||||
$pdo,
|
||||
'resultados',
|
||||
'REDIMIENTO MEDIUM',
|
||||
__DIR__ . '/../db/migrations/088_update_resultados_enum_to_include_redimiento_medium.sql'
|
||||
);
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ $stmt_videos = $db->query("SELECT mv.*, p.nombre as nombre_producto
|
||||
$videos = $stmt_videos->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$statusOptions = ['PENDIENTE', 'EN PROCESO', 'TERMINADO', 'OBSERVADO', 'APROBADO', 'PUBLICADO'];
|
||||
$resultOptions = ['DESCONTINUADO', 'REDIMIENTO MEDIUM', 'BUEN RENDIMIENTO'];
|
||||
$isAdminUser = in_array($_SESSION['user_role'] ?? ($_SESSION['role'] ?? ''), ['Administrador', 'admin'], true);
|
||||
|
||||
function marketing_status_badge_class(?string $estado): string
|
||||
{
|
||||
@ -33,6 +35,16 @@ function marketing_status_badge_class(?string $estado): string
|
||||
};
|
||||
}
|
||||
|
||||
function marketing_result_row_class(?string $resultados): string
|
||||
{
|
||||
return match (strtoupper(trim((string)$resultados))) {
|
||||
'DESCONTINUADO' => 'video-row-result-descontinuado',
|
||||
'REDIMIENTO MEDIUM' => 'video-row-result-medium',
|
||||
'BUEN RENDIMIENTO' => 'video-row-result-buen-rendimiento',
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
// Contadores de estado
|
||||
$stats = [
|
||||
'TOTAL' => count($videos),
|
||||
@ -193,7 +205,8 @@ foreach ($videos as $v) {
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
.status-select {
|
||||
.status-select,
|
||||
.result-select {
|
||||
min-width: 160px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
@ -206,18 +219,26 @@ foreach ($videos as $v) {
|
||||
padding: 0.55rem 2.25rem 0.55rem 0.9rem;
|
||||
box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.06);
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
|
||||
background-color: #f8fafc;
|
||||
color: #1f2937;
|
||||
}
|
||||
.status-select:hover {
|
||||
.status-select:hover,
|
||||
.result-select:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 16px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
.status-select:focus {
|
||||
.status-select:focus,
|
||||
.result-select:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(67, 97, 238, 0.16);
|
||||
}
|
||||
.status-select:disabled {
|
||||
.status-select:disabled,
|
||||
.result-select:disabled {
|
||||
opacity: 0.8;
|
||||
cursor: wait;
|
||||
}
|
||||
.result-select[data-readonly="1"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.status-select[data-status="PENDIENTE"] {
|
||||
background-color: #ffe082;
|
||||
color: #5f4b00;
|
||||
@ -242,9 +263,46 @@ foreach ($videos as $v) {
|
||||
background-color: #dfe4ea;
|
||||
color: #111827;
|
||||
}
|
||||
.status-feedback {
|
||||
.result-select[data-resultados="DESCONTINUADO"] {
|
||||
background-color: #ffe0e6;
|
||||
color: #b42318;
|
||||
}
|
||||
.result-select[data-resultados="REDIMIENTO MEDIUM"] {
|
||||
background-color: #fff3cd;
|
||||
color: #8a6d00;
|
||||
}
|
||||
.result-select[data-resultados="BUEN RENDIMIENTO"] {
|
||||
background-color: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
.status-feedback,
|
||||
.resultados-feedback {
|
||||
min-height: 18px;
|
||||
}
|
||||
.video-row-result-descontinuado > td,
|
||||
.video-row-result-descontinuado > th {
|
||||
background-color: rgba(239, 68, 68, 0.11) !important;
|
||||
}
|
||||
.video-row-result-medium > td,
|
||||
.video-row-result-medium > th {
|
||||
background-color: rgba(255, 243, 205, 0.26) !important;
|
||||
}
|
||||
.video-row-result-buen-rendimiento > td,
|
||||
.video-row-result-buen-rendimiento > th {
|
||||
background-color: rgba(34, 197, 94, 0.11) !important;
|
||||
}
|
||||
.video-row-result-descontinuado:hover > td,
|
||||
.video-row-result-descontinuado:hover > th {
|
||||
background-color: rgba(239, 68, 68, 0.16) !important;
|
||||
}
|
||||
.video-row-result-medium:hover > td,
|
||||
.video-row-result-medium:hover > th {
|
||||
background-color: rgba(255, 243, 205, 0.34) !important;
|
||||
}
|
||||
.video-row-result-buen-rendimiento:hover > td,
|
||||
.video-row-result-buen-rendimiento:hover > th {
|
||||
background-color: rgba(34, 197, 94, 0.16) !important;
|
||||
}
|
||||
.editable:hover {
|
||||
background-color: #f8f9fa;
|
||||
cursor: pointer;
|
||||
@ -374,7 +432,7 @@ foreach ($videos as $v) {
|
||||
<div>
|
||||
<h2 class="mb-0 fw-bold" style="color: #2b2d42;">Producción de Video</h2>
|
||||
<p class="text-muted small mb-1">Gestión de activos y flujo de trabajo creativo</p>
|
||||
<p class="text-muted small mb-0">La columna <strong>Observación</strong> se guarda al salir del campo y <strong>Estado</strong> al cambiar la opción.</p>
|
||||
<p class="text-muted small mb-0">Las columnas <strong>Observación</strong> y <strong>Estado</strong> se guardan al salir del campo o al cambiar la opción. <strong>Resultados</strong> solo puede editarlo el rol <strong>Administrador</strong> y puedes volver a <strong>Sin definir</strong> para limpiar la selección.</p>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="bg-white p-1 rounded-3 shadow-sm d-flex border">
|
||||
@ -421,20 +479,22 @@ foreach ($videos as $v) {
|
||||
<th class="text-center"><i class="fas fa-file-image me-1"></i> Link Flyer</th>
|
||||
<th><i class="fas fa-comment-dots me-1"></i> Observación</th>
|
||||
<th class="text-center"><i class="fas fa-tasks me-1"></i> Estado</th>
|
||||
<th class="text-center"><i class="fas fa-chart-line me-1"></i> Resultados<?php if (!$isAdminUser): ?> <span class="badge bg-secondary ms-1">Solo admin</span><?php endif; ?></th>
|
||||
<th class="text-center"><i class="fas fa-cog me-1"></i> Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($videos)): ?>
|
||||
<tr>
|
||||
<td colspan="14" class="text-center py-5 text-muted">
|
||||
<td colspan="15" class="text-center py-5 text-muted">
|
||||
<i class="fas fa-video-slash fa-3x mb-3 d-block opacity-25"></i>
|
||||
No hay pedidos registrados.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($videos as $v): ?>
|
||||
<tr>
|
||||
<?php $resultadoValue = strtoupper(trim((string)($v['resultados'] ?? ''))); ?>
|
||||
<tr id="video-row-<?php echo $v['id']; ?>" class="<?php echo marketing_result_row_class($resultadoValue); ?>">
|
||||
<td class="text-center fw-bold text-primary"><?php echo $v['orden']; ?></td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
@ -557,6 +617,7 @@ foreach ($videos as $v) {
|
||||
<select
|
||||
class="form-select form-select-sm status-select"
|
||||
data-id="<?php echo $v['id']; ?>"
|
||||
data-field="estado"
|
||||
data-status="<?php echo htmlspecialchars($v['estado'] ?? ''); ?>"
|
||||
aria-label="Estado del pedido <?php echo (int)$v['id']; ?>">
|
||||
<?php foreach ($statusOptions as $statusOption): ?>
|
||||
@ -567,6 +628,23 @@ foreach ($videos as $v) {
|
||||
</select>
|
||||
<div class="small mt-1 status-feedback" id="status-feedback-<?php echo $v['id']; ?>"></div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<select
|
||||
class="form-select form-select-sm result-select"
|
||||
data-id="<?php echo $v['id']; ?>"
|
||||
data-field="resultados"
|
||||
data-resultados="<?php echo htmlspecialchars($resultadoValue); ?>"
|
||||
aria-label="Resultados del pedido <?php echo (int)$v['id']; ?>"
|
||||
<?php echo $isAdminUser ? '' : 'data-readonly="1" disabled aria-disabled="true" title="Solo un administrador puede cambiar este campo"'; ?>>
|
||||
<option value="" <?php echo ($resultadoValue === '') ? 'selected' : ''; ?>>Sin definir</option>
|
||||
<?php foreach ($resultOptions as $resultOption): ?>
|
||||
<option value="<?php echo htmlspecialchars($resultOption); ?>" <?php echo ($resultadoValue === $resultOption) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($resultOption); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="small mt-1 resultados-feedback" id="resultados-feedback-<?php echo $v['id']; ?>"></div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="d-flex justify-content-center gap-1">
|
||||
<button class="btn btn-sm btn-light border shadow-sm" data-video-id="<?php echo $v['id']; ?>" data-video-payload="<?php echo htmlspecialchars(json_encode($v, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT), ENT_QUOTES, 'UTF-8'); ?>" onclick="gestionarVideoFromButton(this)" title="Editar">
|
||||
@ -787,7 +865,7 @@ foreach ($videos as $v) {
|
||||
</div>
|
||||
|
||||
<!-- Modal Gestionar Video -->
|
||||
<div class="modal fade" id="gestionarVideoModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="gestionarVideoModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<form action="save_marketing_video.php" method="POST">
|
||||
@ -1065,17 +1143,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
});
|
||||
|
||||
const statusSelects = document.querySelectorAll('.status-select');
|
||||
statusSelects.forEach(select => {
|
||||
const workflowSelects = document.querySelectorAll('.status-select, .result-select');
|
||||
workflowSelects.forEach(select => {
|
||||
const field = select.dataset.field || 'estado';
|
||||
const feedbackBaseClass = field === 'estado' ? 'status-feedback' : 'resultados-feedback';
|
||||
const feedback = getWorkflowFeedbackElement(field, select.dataset.id);
|
||||
let lastSavedValue = select.value;
|
||||
const feedback = document.getElementById(`status-feedback-${select.dataset.id}`);
|
||||
|
||||
const setFeedback = (message, className = 'text-muted') => {
|
||||
if (!feedback) return;
|
||||
feedback.className = `small mt-1 status-feedback ${className}`;
|
||||
feedback.className = `small mt-1 ${feedbackBaseClass} ${className}`;
|
||||
feedback.textContent = message;
|
||||
};
|
||||
|
||||
if (field === 'estado') {
|
||||
select.dataset.status = select.value;
|
||||
} else if (field === 'resultados') {
|
||||
select.dataset.resultados = select.value;
|
||||
updateVideoResultRow(select.dataset.id, select.value);
|
||||
}
|
||||
|
||||
select.addEventListener('change', function() {
|
||||
const newValue = select.value;
|
||||
const previousValue = lastSavedValue;
|
||||
@ -1085,7 +1172,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
select.disabled = true;
|
||||
select.dataset.status = newValue;
|
||||
if (field === 'estado') {
|
||||
select.dataset.status = newValue;
|
||||
} else if (field === 'resultados') {
|
||||
select.dataset.resultados = newValue;
|
||||
}
|
||||
setFeedback('Guardando...', 'text-primary');
|
||||
|
||||
fetch('update_marketing_video_field.php', {
|
||||
@ -1093,7 +1184,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
id: select.dataset.id,
|
||||
field: 'estado',
|
||||
field,
|
||||
value: newValue
|
||||
})
|
||||
})
|
||||
@ -1101,10 +1192,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
lastSavedValue = newValue;
|
||||
syncVideoPayload(select.dataset.id, 'estado', newValue);
|
||||
updateCardStatusBadge(select.dataset.id, newValue);
|
||||
updateStatusCounter(previousValue, -1);
|
||||
updateStatusCounter(newValue, 1);
|
||||
syncVideoPayload(select.dataset.id, field, newValue);
|
||||
|
||||
if (field === 'estado') {
|
||||
updateCardStatusBadge(select.dataset.id, newValue);
|
||||
updateStatusCounter(previousValue, -1);
|
||||
updateStatusCounter(newValue, 1);
|
||||
} else if (field === 'resultados') {
|
||||
updateVideoResultRow(select.dataset.id, newValue);
|
||||
}
|
||||
|
||||
setFeedback('Guardado', 'text-success');
|
||||
setTimeout(() => {
|
||||
if (feedback && feedback.textContent === 'Guardado') {
|
||||
@ -1113,14 +1210,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}, 2000);
|
||||
} else {
|
||||
select.value = previousValue;
|
||||
select.dataset.status = previousValue;
|
||||
if (field === 'estado') {
|
||||
select.dataset.status = previousValue;
|
||||
} else if (field === 'resultados') {
|
||||
select.dataset.resultados = previousValue;
|
||||
}
|
||||
setFeedback('Error al guardar', 'text-danger');
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
select.value = previousValue;
|
||||
select.dataset.status = previousValue;
|
||||
if (field === 'estado') {
|
||||
select.dataset.status = previousValue;
|
||||
} else if (field === 'resultados') {
|
||||
select.dataset.resultados = previousValue;
|
||||
}
|
||||
setFeedback('Error al guardar', 'text-danger');
|
||||
})
|
||||
.finally(() => {
|
||||
@ -1195,7 +1300,11 @@ function gestionarVideo(video) {
|
||||
fillLinks('edit_links_landing_container', 'link_landing[]', video.link_landing);
|
||||
fillLinks('edit_links_flyer_container', 'link_flyer[]', video.link_flyer);
|
||||
|
||||
var myModal = new bootstrap.Modal(document.getElementById('gestionarVideoModal'));
|
||||
var modalElement = document.getElementById('gestionarVideoModal');
|
||||
var myModal = bootstrap.Modal.getOrCreateInstance(modalElement, {
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
myModal.show();
|
||||
}
|
||||
|
||||
@ -1205,6 +1314,27 @@ function eliminarVideo(id) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateVideoResultRow(videoId, value) {
|
||||
const row = document.getElementById(`video-row-${videoId}`);
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.classList.remove('video-row-result-descontinuado', 'video-row-result-medium', 'video-row-result-buen-rendimiento');
|
||||
if (value === 'DESCONTINUADO') {
|
||||
row.classList.add('video-row-result-descontinuado');
|
||||
} else if (value === 'REDIMIENTO MEDIUM') {
|
||||
row.classList.add('video-row-result-medium');
|
||||
} else if (value === 'BUEN RENDIMIENTO') {
|
||||
row.classList.add('video-row-result-buen-rendimiento');
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkflowFeedbackElement(field, videoId) {
|
||||
const prefix = field === 'estado' ? 'status' : field;
|
||||
return document.getElementById(`${prefix}-feedback-${videoId}`);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const editableCells = document.querySelectorAll('.editable');
|
||||
|
||||
|
||||
@ -35,9 +35,12 @@ if (is_array($value) || is_object($value)) {
|
||||
}
|
||||
|
||||
$value = is_string($value) ? trim($value) : (string)$value;
|
||||
if ($field === 'resultados' && $value === '') {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
// Whitelist allowed fields for security
|
||||
$allowed_fields = ['material', 'estado', 'angulo_video', 'observacion'];
|
||||
$allowed_fields = ['material', 'estado', 'angulo_video', 'observacion', 'resultados'];
|
||||
if (!in_array($field, $allowed_fields, true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Campo no permitido.']);
|
||||
@ -53,6 +56,24 @@ if ($field === 'estado') {
|
||||
}
|
||||
}
|
||||
|
||||
if ($field === 'resultados') {
|
||||
$userRole = $_SESSION['user_role'] ?? ($_SESSION['role'] ?? '');
|
||||
$isAdminUser = in_array($userRole, ['Administrador', 'admin'], true);
|
||||
|
||||
if (!$isAdminUser) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Solo un administrador puede cambiar resultados.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$allowedResults = ['DESCONTINUADO', 'REDIMIENTO MEDIUM', 'BUEN RENDIMIENTO'];
|
||||
if ($value !== null && !in_array($value, $allowedResults, true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Resultado no permitido.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
marketing_ensure_schema($pdo);
|
||||
@ -69,4 +90,4 @@ try {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error al actualizar la base de datos: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user