@@ -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();
}
}
diff --git a/includes/callcenter_test_helpers.php b/includes/callcenter_test_helpers.php
index 426603f8..61de05f7 100644
--- a/includes/callcenter_test_helpers.php
+++ b/includes/callcenter_test_helpers.php
@@ -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
diff --git a/includes/marketing_bootstrap.php b/includes/marketing_bootstrap.php
index e6eb58e7..2a7363c6 100644
--- a/includes/marketing_bootstrap.php
+++ b/includes/marketing_bootstrap.php
@@ -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'
+ );
}
diff --git a/marketing_produccion.php b/marketing_produccion.php
index 206d9ac1..47597412 100644
--- a/marketing_produccion.php
+++ b/marketing_produccion.php
@@ -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) {
Producción de Video
Gestión de activos y flujo de trabajo creativo
-
La columna Observación se guarda al salir del campo y Estado al cambiar la opción.
+
Las columnas Observación y Estado se guardan al salir del campo o al cambiar la opción. Resultados solo puede editarlo el rol Administrador y puedes volver a Sin definir para limpiar la selección.
@@ -421,20 +479,22 @@ foreach ($videos as $v) {
Link Flyer
Observación
Estado
+
Resultados Solo admin
Acción
-
+
No hay pedidos registrados.
-
+
+
@@ -557,6 +617,7 @@ foreach ($videos as $v) {
@@ -567,6 +628,23 @@ foreach ($videos as $v) {
+
+ >
+ >Sin definir
+
+ >
+
+
+
+
+
+
@@ -787,7 +865,7 @@ foreach ($videos as $v) {
-