Autosave: 20260518-233649

This commit is contained in:
Flatlogic Bot 2026-05-18 23:36:50 +00:00
parent bbd26aa303
commit a8d2f9cb0a
9 changed files with 794 additions and 173 deletions

View File

@ -116,7 +116,7 @@ body {
body.sidebar-active .content {
/* Don't push content, but apply a visual effect */
filter: blur(3px) brightness(0.6);
pointer-events: none; /* Prevent interaction with content when sidebar is open */
/* pointer-events: none; REMOVED to allow interaction even if sidebar is active */
}
/* The body itself gets an overlay to darken it */

View File

@ -1,4 +1,6 @@
<?php
header("Location: calculo_costos_v3.php");
exit();
$pageTitle = "Cálculo de Costos V2";
include 'db/config.php';
include 'layout_header.php';
@ -24,210 +26,244 @@ $costos = $stmt->fetchAll(PDO::FETCH_ASSOC);
<style>
.table-v2 {
font-size: 0.85rem;
border-collapse: separate;
border-spacing: 0 5px;
}
.table-v2 th {
background-color: #f1f3f5;
border-bottom: 2px solid #dee2e6;
white-space: nowrap;
padding: 12px 8px;
position: sticky;
top: 0;
z-index: 10;
}
.table-v2 td {
vertical-align: middle;
padding: 4px;
padding: 8px 4px;
background-color: #fff;
}
.input-cell {
width: 100%;
min-width: 80px;
padding: 6px;
border: 1px solid #ced4da;
border-radius: 4px;
min-width: 85px;
padding: 8px;
border: 2px solid #e9ecef;
border-radius: 6px;
text-align: center;
font-size: 0.85rem;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s;
cursor: text !important;
pointer-events: auto !important;
}
.input-cell:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
outline: 0;
background-color: #fff;
}
.input-cell:hover {
border-color: #adb5bd;
}
.input-cell.saving {
background-color: #fff3cd;
border-color: #ffc107;
background-color: #fff3cd !important;
border-color: #ffc107 !important;
}
.input-cell.saved {
background-color: #d1e7dd;
border-color: #198754;
background-color: #d1e7dd !important;
border-color: #198754 !important;
}
.recaudo-v2 {
background-color: #f8f9fa;
font-weight: bold;
text-align: center;
padding: 8px;
border-radius: 4px;
min-width: 90px;
transition: all 0.3s;
padding: 10px;
border-radius: 6px;
min-width: 100px;
border: 1px solid #dee2e6;
}
.img-v2 {
width: 40px;
height: 40px;
width: 45px;
height: 45px;
object-fit: cover;
border-radius: 4px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.btn-save-row {
padding: 6px 12px;
.btn-action {
width: 32px;
height: 32px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
}
.editable-label {
font-size: 0.7rem;
color: #6c757d;
display: block;
margin-bottom: 2px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
</style>
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-0">Cálculo de Costos <span class="badge bg-warning text-dark">Versión 2</span></h2>
<p class="text-muted">Edición directa mediante cuadros de texto. Escribe y presiona "Guardar".</p>
</div>
<div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNuevoV2">
<i class="fas fa-plus me-2"></i> Nuevo Producto
</button>
</div>
<div class="d-flex justify-content-between align-items-center mb-4 mt-2">
<div>
<p class="text-muted mb-0">Edición directa: Escribe en los cuadros y los cambios se guardarán al salir del cuadro.</p>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary" onclick="location.reload()">
<i class="fas fa-sync-alt"></i> Actualizar Todo
</button>
<button type="button" class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#modalNuevoV2">
<i class="fas fa-plus me-2"></i> Nuevo Producto
</button>
</div>
</div>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
¡Cambios guardados correctamente!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success alert-dismissible fade show shadow-sm" role="alert">
<i class="fas fa-check-circle me-2"></i> ¡Operación realizada con éxito!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="card shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-v2 mb-0">
<thead>
<tr>
<th class="text-center">Orden</th>
<th>Producto</th>
<th class="text-center">Imagen</th>
<th class="text-center">Costo Prod.</th>
<th class="text-center">Film</th>
<th class="text-center">Asesora</th>
<th class="text-center">Delivery</th>
<th class="text-center">Publicidad</th>
<th class="text-center bg-light">Inversión Total</th>
<th class="text-center">Promo 1</th>
<th class="text-center">Recaudo 1</th>
<th class="text-center">Promo 2</th>
<th class="text-center">Recaudo 2</th>
<th class="text-center">Promo 3</th>
<th class="text-center">Recaudo 3</th>
<th class="text-center">Acción</th>
</tr>
</thead>
<tbody>
<?php foreach ($costos as $c): ?>
<?php
$inversion_total = ($c['costo_producto'] ?? 0) +
($c['costo_fijo_film'] ?? 0) +
($c['comision_asesora'] ?? 0) +
($c['delivery'] ?? 0) +
($c['costo_publicitario'] ?? 0);
<div class="card shadow-sm border-0">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 70vh;">
<table class="table table-v2 mb-0">
<thead>
<tr>
<th class="text-center">Orden</th>
<th>Producto</th>
<th class="text-center">Imagen</th>
<th class="text-center">Costo Prod.</th>
<th class="text-center">Film</th>
<th class="text-center">Asesora</th>
<th class="text-center">Delivery</th>
<th class="text-center">Publicidad</th>
<th class="text-center bg-light">Inversión Total</th>
<th class="text-center">Promo 1</th>
<th class="text-center">Recaudo 1</th>
<th class="text-center">Promo 2</th>
<th class="text-center">Recaudo 2</th>
<th class="text-center">Promo 3</th>
<th class="text-center">Recaudo 3</th>
<th class="text-center">Acción</th>
</tr>
</thead>
<tbody>
<?php foreach ($costos as $c): ?>
<?php
$inversion_total = ($c['costo_producto'] ?? 0) +
($c['costo_fijo_film'] ?? 0) +
($c['comision_asesora'] ?? 0) +
($c['delivery'] ?? 0) +
($c['costo_publicitario'] ?? 0);
function getVal($promo) {
preg_match('/[\d.]+/', $promo, $matches);
return isset($matches[0]) ? floatval($matches[0]) : 0;
}
function getVal($promo) {
if (empty($promo)) return 0;
preg_match('/[\d.]+/', $promo, $matches);
return isset($matches[0]) ? floatval($matches[0]) : 0;
}
$p1 = getVal($c['promo_1']);
$p2 = getVal($c['promo_2']);
$p3 = getVal($c['promo_3']);
$p1 = getVal($c['promo_1']);
$p2 = getVal($c['promo_2']);
$p3 = getVal($c['promo_3']);
$r1 = $p1 > 0 ? $p1 - $inversion_total : null;
$r2 = $p2 > 0 ? $p2 - $inversion_total : null;
$r3 = $p3 > 0 ? $p3 - $inversion_total : null;
?>
<tr id="row-<?php echo $c['id']; ?>" data-id="<?php echo $c['id']; ?>">
<td class="text-center fw-bold"><?php echo $c['orden']; ?></td>
<td style="max-width: 150px;" class="text-truncate" title="<?php echo htmlspecialchars($c['nombre_producto']); ?>">
$r1 = $p1 > 0 ? $p1 - $inversion_total : null;
$r2 = $p2 > 0 ? $p2 - $inversion_total : null;
$r3 = $p3 > 0 ? $p3 - $inversion_total : null;
?>
<tr id="row-<?php echo $c['id']; ?>" data-id="<?php echo $c['id']; ?>">
<td class="text-center fw-bold text-primary"><?php echo $c['orden']; ?></td>
<td style="max-width: 140px;">
<div class="text-truncate fw-bold" title="<?php echo htmlspecialchars($c['nombre_producto']); ?>">
<?php echo htmlspecialchars($c['nombre_producto'] ?: 'General'); ?>
</td>
<td class="text-center">
<?php if ($c['foto_producto']): ?>
<img src="<?php echo $c['foto_producto']; ?>" class="img-v2">
<?php endif; ?>
</td>
<td><input type="number" step="0.01" class="input-cell" data-field="costo_producto" value="<?php echo $c['costo_producto']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_producto', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="costo_fijo_film" value="<?php echo $c['costo_fijo_film']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_fijo_film', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="comision_asesora" value="<?php echo $c['comision_asesora']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'comision_asesora', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="delivery" value="<?php echo $c['delivery']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'delivery', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="costo_publicitario" value="<?php echo $c['costo_publicitario']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_publicitario', this.value)"></td>
<td class="text-center">
<div class="recaudo-v2 bg-light fw-bold" id="total-<?php echo $c['id']; ?>">
S/ <?php echo number_format($inversion_total, 2); ?>
</div>
</td>
<td class="text-center">
<?php if ($c['foto_producto']): ?>
<img src="<?php echo $c['foto_producto']; ?>" class="img-v2">
<?php else: ?>
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width:45px; height:45px;">
<i class="fas fa-image text-muted"></i>
</div>
</td>
<td><input type="text" class="input-cell" data-field="promo_1" value="<?php echo htmlspecialchars($c['promo_1']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_1', this.value)"></td>
<td><div class="recaudo-v2 recaudo-1 <?php echo $r1 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1-<?php echo $c['id']; ?>"><?php echo $r1 !== null ? 'S/ '.number_format($r1, 2) : '-'; ?></div></td>
<td><input type="text" class="input-cell" data-field="promo_2" value="<?php echo htmlspecialchars($c['promo_2']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_2', this.value)"></td>
<td><div class="recaudo-v2 recaudo-2 <?php echo $r2 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2-<?php echo $c['id']; ?>"><?php echo $r2 !== null ? 'S/ '.number_format($r2, 2) : '-'; ?></div></td>
<td><input type="text" class="input-cell" data-field="promo_3" value="<?php echo htmlspecialchars($c['promo_3']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_3', this.value)"></td>
<td><div class="recaudo-v2 recaudo-3 <?php echo $r3 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3-<?php echo $c['id']; ?>"><?php echo $r3 !== null ? 'S/ '.number_format($r3, 2) : '-'; ?></div></td>
<td class="text-center">
<div class="d-flex gap-1">
<button class="btn btn-success btn-sm btn-save-row" onclick="location.reload()" title="Refrescar cálculos">
<i class="fas fa-sync-alt"></i>
</button>
<button class="btn btn-danger btn-sm" onclick="eliminarRow(<?php echo $c['id']; ?>)">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</td>
<td><input type="number" step="0.01" class="input-cell" data-field="costo_producto" value="<?php echo $c['costo_producto']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_producto', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="costo_fijo_film" value="<?php echo $c['costo_fijo_film']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_fijo_film', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="comision_asesora" value="<?php echo $c['comision_asesora']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'comision_asesora', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="delivery" value="<?php echo $c['delivery']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'delivery', this.value)"></td>
<td><input type="number" step="0.01" class="input-cell" data-field="costo_publicitario" value="<?php echo $c['costo_publicitario']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_publicitario', this.value)"></td>
<td class="text-center">
<div class="recaudo-v2 bg-light fw-bold text-dark" id="total-<?php echo $c['id']; ?>">
S/ <?php echo number_format($inversion_total, 2); ?>
</div>
</td>
<td><input type="text" class="input-cell" data-field="promo_1" value="<?php echo htmlspecialchars($c['promo_1']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_1', this.value)"></td>
<td><div class="recaudo-v2 recaudo-1 <?php echo $r1 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1-<?php echo $c['id']; ?>"><?php echo $r1 !== null ? 'S/ '.number_format($r1, 2) : '-'; ?></div></td>
<td><input type="text" class="input-cell" data-field="promo_2" value="<?php echo htmlspecialchars($c['promo_2']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_2', this.value)"></td>
<td><div class="recaudo-v2 recaudo-2 <?php echo $r2 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2-<?php echo $c['id']; ?>"><?php echo $r2 !== null ? 'S/ '.number_format($r2, 2) : '-'; ?></div></td>
<td><input type="text" class="input-cell" data-field="promo_3" value="<?php echo htmlspecialchars($c['promo_3']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_3', this.value)"></td>
<td><div class="recaudo-v2 recaudo-3 <?php echo $r3 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3-<?php echo $c['id']; ?>"><?php echo $r3 !== null ? 'S/ '.number_format($r3, 2) : '-'; ?></div></td>
<td class="text-center">
<button class="btn btn-danger btn-action" onclick="eliminarRow(<?php echo $c['id']; ?>)" title="Eliminar">
<i class="fas fa-trash-alt"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Nuevo -->
<div class="modal fade" id="modalNuevoV2" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal fade" id="modalNuevoV2" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<form action="save_marketing_video.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="redirect" value="calculo_costos_v2.php?success=1">
<div class="modal-header">
<h5 class="modal-title">Nuevo Producto</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
<div class="modal-header bg-primary text-white">
<h5 class="modal-title"><i class="fas fa-plus-circle me-2"></i> Nuevo Producto para Costos</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label">Producto</label>
<select name="producto_id" id="select_producto_nuevo" class="form-select" required onchange="fetchProductCost(this.value)">
<label class="form-label fw-bold">Seleccionar Producto</label>
<select name="producto_id" id="select_producto_nuevo" class="form-select form-select-lg" required onchange="fetchProductCost(this.value)">
<option value="">Seleccionar...</option>
<?php foreach ($productos_select as $p): ?>
<option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['nombre']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Costo del Producto (S/)</label>
<input type="number" name="costo_producto" id="costo_producto_nuevo" class="form-control" step="0.01" placeholder="0.00">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Costo Base (S/)</label>
<input type="number" name="costo_producto" id="costo_producto_nuevo" class="form-control" step="0.01" placeholder="0.00">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Orden de Lista</label>
<input type="number" name="orden" class="form-control" value="1">
</div>
</div>
<div class="mb-3">
<label class="form-label">Orden</label>
<input type="number" name="orden" class="form-control" value="1">
</div>
<div class="mb-3">
<label class="form-label">Imagen</label>
<div class="mb-0">
<label class="form-label fw-bold">Imagen del Producto</label>
<input type="file" name="foto_producto" class="form-control">
<small class="text-muted">Opcional. Si no se sube, se usará la del producto.</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
<button type="submit" class="btn btn-primary">Guardar</button>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary px-4">Crear Registro</button>
</div>
</form>
</div>
@ -235,6 +271,8 @@ $costos = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div>
<script>
console.log("Script de Cálculo de Costos V2 cargado");
function fetchProductCost(productId) {
if (!productId) return;
@ -252,6 +290,8 @@ function updateRow(id, field, value) {
const row = document.getElementById(`row-${id}`);
const input = row.querySelector(`[data-field="${field}"]`);
if (!input) return;
input.classList.add('saving');
console.log(`Actualizando ${field} para ID ${id} con valor ${value}`);
@ -265,54 +305,64 @@ function updateRow(id, field, value) {
input.classList.remove('saving');
if (data.success) {
input.classList.add('saved');
setTimeout(() => input.classList.remove('saved'), 1000);
setTimeout(() => input.classList.remove('saved'), 1500);
// Recalcular todo en la fila
recalculateRow(id);
} else {
alert('Error al guardar: ' + data.error);
alert('Error al guardar: ' + (data.error || 'Error desconocido'));
}
})
.catch(error => {
input.classList.remove('saving');
console.error('Error:', error);
alert('Error de conexión');
alert('Error de conexión al servidor');
});
}
function recalculateRow(id) {
const row = document.getElementById(`row-${id}`);
if (!row) return;
const fields = ['costo_producto', 'costo_fijo_film', 'comision_asesora', 'delivery', 'costo_publicitario'];
let total = 0;
fields.forEach(f => {
const val = parseFloat(row.querySelector(`[data-field="${f}"]`).value) || 0;
total += val;
const el = row.querySelector(`[data-field="${f}"]`);
if (el) {
total += parseFloat(el.value) || 0;
}
});
document.getElementById(`total-${id}`).innerText = 'S/ ' + total.toFixed(2);
const totalEl = document.getElementById(`total-${id}`);
if (totalEl) {
totalEl.innerText = 'S/ ' + total.toFixed(2);
}
// Recalcular recaudos
for (let i = 1; i <= 3; i++) {
const promoVal = row.querySelector(`[data-field="promo_${i}"]`).value;
const promoEl = row.querySelector(`[data-field="promo_${i}"]`);
const recaudoEl = document.getElementById(`r${i}-${id}`);
const match = promoVal.match(/[\d.]+/);
const pVal = match ? parseFloat(match[0]) : 0;
if (pVal > 0) {
const recaudo = pVal - total;
recaudoEl.innerText = 'S/ ' + recaudo.toFixed(2);
recaudoEl.className = 'recaudo-v2 ' + (recaudo >= 0 ? 'text-success' : 'text-danger');
} else {
recaudoEl.innerText = '-';
recaudoEl.className = 'recaudo-v2 text-muted';
if (promoEl && recaudoEl) {
const promoVal = promoEl.value;
const match = promoVal.match(/[\d.]+/);
const pVal = match ? parseFloat(match[0]) : 0;
if (pVal > 0) {
const recaudo = pVal - total;
recaudoEl.innerText = 'S/ ' + recaudo.toFixed(2);
recaudoEl.className = 'recaudo-v2 ' + (recaudo >= 0 ? 'text-success' : 'text-danger');
} else {
recaudoEl.innerText = '-';
recaudoEl.className = 'recaudo-v2 text-muted';
}
}
}
}
function eliminarRow(id) {
if (confirm('¿Eliminar este producto?')) {
if (confirm('¿Estás seguro de que deseas eliminar este registro de costos?')) {
window.location.href = 'delete_marketing_video.php?id=' + id + '&redirect=calculo_costos_v2.php';
}
}

398
calculo_costos_v3.php Normal file
View File

@ -0,0 +1,398 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['user_role']) || !in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
header('Location: dashboard.php');
exit();
}
$pageTitle = "Cálculo de Costos V3";
include 'db/config.php';
include 'layout_header.php';
$db = db();
// Obtener productos para el select del modal
$stmt_products = $db->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
$productos_select = $stmt_products->fetchAll(PDO::FETCH_ASSOC);
// Obtener videos y sus costos asociados de las tablas V3
$stmt = $db->query("SELECT mv.id, mv.orden, mv.foto_producto, p.nombre as nombre_producto,
mc.costo_producto, mc.costo_fijo_film, mc.comision_asesora,
mc.delivery, mc.costo_publicitario, mc.inversion_total,
mc.promo_1, mc.promo_2, mc.promo_3,
mc.comision_asesora_provincia, mc.delivery_provincia
FROM marketing_videos_v3 mv
LEFT JOIN products p ON mv.producto_id = p.id
LEFT JOIN marketing_costos_v3 mc ON mv.id = mc.video_id
ORDER BY mv.orden ASC, mv.id DESC");
$costos = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<style>
.table-v3 {
font-size: 0.8rem;
border-collapse: separate;
border-spacing: 0 5px;
}
.table-v3 th {
background-color: #f8f9fa;
border-bottom: 2px solid #dee2e6;
white-space: nowrap;
padding: 10px 5px;
position: sticky;
top: 0;
z-index: 10;
}
.table-v3 td {
vertical-align: middle;
padding: 6px 3px;
background-color: #fff;
}
.input-cell {
width: 100%;
min-width: 70px;
padding: 6px;
border: 2px solid #e9ecef;
border-radius: 6px;
text-align: center;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s;
}
.input-cell:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
outline: 0;
}
.input-cell.saving { background-color: #fff3cd !important; border-color: #ffc107 !important; }
.input-cell.saved { background-color: #d1e7dd !important; border-color: #198754 !important; }
.recaudo-v3 {
background-color: #f8f9fa;
font-weight: bold;
text-align: center;
padding: 6px 4px;
border-radius: 6px;
min-width: 85px;
border: 1px solid #dee2e6;
font-size: 0.8rem;
}
.recaudo-label {
font-size: 0.65rem;
display: block;
color: #6c757d;
margin-bottom: 2px;
}
.img-v3 {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 6px;
}
.btn-action {
width: 28px;
height: 28px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
}
.bg-provincia {
background-color: #fff4e6 !important;
}
.header-provincia {
background-color: #ffe8cc !important;
color: #d9480f !important;
}
</style>
<div class="d-flex justify-content-between align-items-center mb-4 mt-2">
<div>
<p class="text-muted mb-0">Versión V3: Tabla limpia e independiente. Los cambios se guardan automáticamente.</p>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary" onclick="location.reload()">
<i class="fas fa-sync-alt"></i> Actualizar
</button>
<button type="button" class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#modalNuevoV3">
<i class="fas fa-plus me-2"></i> Nuevo Producto
</button>
</div>
</div>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success alert-dismissible fade show shadow-sm" role="alert">
<i class="fas fa-check-circle me-2"></i> ¡Operación realizada con éxito!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="card shadow-sm border-0">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 70vh;">
<table class="table table-v3 mb-0">
<thead>
<tr>
<th class="text-center">Orden</th>
<th>Producto</th>
<th class="text-center">Imagen</th>
<th class="text-center">Costo Prod.</th>
<th class="text-center">Film</th>
<th class="text-center">Asesora (L)</th>
<th class="text-center header-provincia">Asesora (P)</th>
<th class="text-center">Delivery (L)</th>
<th class="text-center header-provincia">Delivery (P)</th>
<th class="text-center">Publicidad</th>
<th class="text-center">Promo 1</th>
<th class="text-center">Recaudo 1</th>
<th class="text-center">Promo 2</th>
<th class="text-center">Recaudo 2</th>
<th class="text-center">Promo 3</th>
<th class="text-center">Recaudo 3</th>
<th class="text-center">Acción</th>
</tr>
</thead>
<tbody>
<?php if (empty($costos)): ?>
<tr>
<td colspan="17" class="text-center py-5 text-muted">
<i class="fas fa-info-circle me-2"></i> No hay productos en esta lista. Haz clic en "Nuevo Producto" para empezar.
</td>
</tr>
<?php endif; ?>
<?php foreach ($costos as $c): ?>
<?php
$costo_prod = ($c['costo_producto'] ?? 0);
$costo_film = ($c['costo_fijo_film'] ?? 0);
$costo_asesora = ($c['comision_asesora'] ?? 0);
$costo_asesora_p = ($c['comision_asesora_provincia'] ?? 0);
$costo_delivery = ($c['delivery'] ?? 0);
$costo_delivery_p = ($c['delivery_provincia'] ?? 0);
$costo_publicidad = ($c['costo_publicitario'] ?? 0);
function getVal($promo) {
if (empty($promo)) return 0;
preg_match('/[\d.]+/', $promo, $matches);
return isset($matches[0]) ? floatval($matches[0]) : 0;
}
$p1 = getVal($c['promo_1']);
$p2 = getVal($c['promo_2']);
$p3 = getVal($c['promo_3']);
// Cálculos Local
$r1_l = $p1 > 0 ? $p1 - ($costo_prod + $costo_film + $costo_asesora + $costo_delivery + $costo_publicidad) : null;
$r2_l = $p2 > 0 ? $p2 - (($costo_prod + $costo_film) * 2 + $costo_asesora + $costo_delivery + $costo_publicidad) : null;
$r3_l = $p3 > 0 ? $p3 - (($costo_prod + $costo_film) * 3 + $costo_asesora + $costo_delivery + $costo_publicidad) : null;
// Cálculos Provincia
$r1_p = $p1 > 0 ? $p1 - ($costo_prod + $costo_film + $costo_asesora_p + $costo_delivery_p + $costo_publicidad) : null;
$r2_p = $p2 > 0 ? $p2 - (($costo_prod + $costo_film) * 2 + $costo_asesora_p + $costo_delivery_p + $costo_publicidad) : null;
$r3_p = $p3 > 0 ? $p3 - (($costo_prod + $costo_film) * 3 + $costo_asesora_p + $costo_delivery_p + $costo_publicidad) : null;
?>
<tr id="row-<?php echo $c['id']; ?>" data-id="<?php echo $c['id']; ?>">
<td class="text-center fw-bold text-primary"><?php echo $c['orden']; ?></td>
<td style="max-width: 120px;">
<div class="text-truncate fw-bold" title="<?php echo htmlspecialchars($c['nombre_producto']); ?>">
<?php echo htmlspecialchars($c['nombre_producto'] ?: 'General'); ?>
</div>
</td>
<td class="text-center">
<?php if ($c['foto_producto']): ?>
<img src="<?php echo $c['foto_producto']; ?>" class="img-v3">
<?php else: ?>
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width:40px; height:40px;">
<i class="fas fa-image text-muted"></i>
</div>
<?php endif; ?>
</td>
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="costo_producto" value="<?php echo $c['costo_producto']; ?>"></td>
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="costo_fijo_film" value="<?php echo $c['costo_fijo_film']; ?>"></td>
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="comision_asesora" value="<?php echo $c['comision_asesora']; ?>"></td>
<td><input type="number" step="0.01" class="input-cell cost-input bg-provincia" data-field="comision_asesora_provincia" value="<?php echo $c['comision_asesora_provincia']; ?>"></td>
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="delivery" value="<?php echo $c['delivery']; ?>"></td>
<td><input type="number" step="0.01" class="input-cell cost-input bg-provincia" data-field="delivery_provincia" value="<?php echo $c['delivery_provincia']; ?>"></td>
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="costo_publicitario" value="<?php echo $c['costo_publicitario']; ?>"></td>
<td><input type="text" class="input-cell promo-input" data-field="promo_1" value="<?php echo htmlspecialchars($c['promo_1']); ?>"></td>
<td>
<div class="recaudo-v3 mb-1 <?php echo $r1_l >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1l-<?php echo $c['id']; ?>">
<span class="recaudo-label">LOCAL</span>
<?php echo $r1_l !== null ? 'S/ '.number_format($r1_l, 2) : '-'; ?>
</div>
<div class="recaudo-v3 bg-provincia <?php echo $r1_p >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1p-<?php echo $c['id']; ?>">
<span class="recaudo-label">PROVINCIA</span>
<?php echo $r1_p !== null ? 'S/ '.number_format($r1_p, 2) : '-'; ?>
</div>
</td>
<td><input type="text" class="input-cell promo-input" data-field="promo_2" value="<?php echo htmlspecialchars($c['promo_2']); ?>"></td>
<td>
<div class="recaudo-v3 mb-1 <?php echo $r2_l >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2l-<?php echo $c['id']; ?>">
<span class="recaudo-label">LOCAL</span>
<?php echo $r2_l !== null ? 'S/ '.number_format($r2_l, 2) : '-'; ?>
</div>
<div class="recaudo-v3 bg-provincia <?php echo $r2_p >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2p-<?php echo $c['id']; ?>">
<span class="recaudo-label">PROVINCIA</span>
<?php echo $r2_p !== null ? 'S/ '.number_format($r2_p, 2) : '-'; ?>
</div>
</td>
<td><input type="text" class="input-cell promo-input" data-field="promo_3" value="<?php echo htmlspecialchars($c['promo_3']); ?>"></td>
<td>
<div class="recaudo-v3 mb-1 <?php echo $r3_l >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3l-<?php echo $c['id']; ?>">
<span class="recaudo-label">LOCAL</span>
<?php echo $r3_l !== null ? 'S/ '.number_format($r3_l, 2) : '-'; ?>
</div>
<div class="recaudo-v3 bg-provincia <?php echo $r3_p >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3p-<?php echo $c['id']; ?>">
<span class="recaudo-label">PROVINCIA</span>
<?php echo $r3_p !== null ? 'S/ '.number_format($r3_p, 2) : '-'; ?>
</div>
</td>
<td class="text-center">
<button class="btn btn-danger btn-action" onclick="eliminarRow(<?php echo $c['id']; ?>)" title="Eliminar">
<i class="fas fa-trash-alt"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Nuevo -->
<div class="modal fade" id="modalNuevoV3" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<form action="save_marketing_video_v3.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="redirect" value="calculo_costos_v3.php?success=1">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title"><i class="fas fa-plus-circle me-2"></i> Nuevo Producto (V3)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label fw-bold">Seleccionar Producto</label>
<select name="producto_id" class="form-select form-select-lg" required onchange="fetchProductCost(this.value)">
<option value="">Seleccionar...</option>
<?php foreach ($productos_select as $p): ?>
<option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['nombre']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Costo Base (S/)</label>
<input type="number" name="costo_producto" id="costo_producto_nuevo_v3" class="form-control" step="0.01" placeholder="0.00">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Orden de Lista</label>
<input type="number" name="orden" class="form-control" value="1">
</div>
</div>
<div class="mb-0">
<label class="form-label fw-bold">Imagen del Producto</label>
<input type="file" name="foto_producto" class="form-control">
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary px-4">Crear Registro</button>
</div>
</form>
</div>
</div>
</div>
<script>
function fetchProductCost(productId) {
if (!productId) return;
fetch(`get_product_details.php?id=${productId}`)
.then(response => response.json())
.then(data => {
if (data.success && data.product.costo) {
document.getElementById('costo_producto_nuevo_v3').value = data.product.costo;
}
});
}
document.querySelectorAll('.input-cell').forEach(input => {
// Actualización instantánea visual
input.addEventListener('input', function() {
const row = this.closest('tr');
recalculateRowVisual(row);
});
// Guardado real al salir del cuadro
input.addEventListener('change', function() {
const row = this.closest('tr');
const id = row.dataset.id;
const field = this.dataset.field;
const value = this.value;
this.classList.add('saving');
fetch('update_marketing_costos_field_v3.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, field, value })
})
.then(response => response.json())
.then(data => {
this.classList.remove('saving');
if (data.success) {
this.classList.add('saved');
setTimeout(() => this.classList.remove('saved'), 1000);
}
});
});
});
function recalculateRowVisual(row) {
const id = row.dataset.id;
const costProd = parseFloat(row.querySelector('[data-field="costo_producto"]').value) || 0;
const costFilm = parseFloat(row.querySelector('[data-field="costo_fijo_film"]').value) || 0;
const costAsesora = parseFloat(row.querySelector('[data-field="comision_asesora"]').value) || 0;
const costAsesoraP = parseFloat(row.querySelector('[data-field="comision_asesora_provincia"]').value) || 0;
const costDelivery = parseFloat(row.querySelector('[data-field="delivery"]').value) || 0;
const costDeliveryP = parseFloat(row.querySelector('[data-field="delivery_provincia"]').value) || 0;
const costPublicidad = parseFloat(row.querySelector('[data-field="costo_publicitario"]').value) || 0;
for (let i = 1; i <= 3; i++) {
const promoVal = row.querySelector(`[data-field="promo_${i}"]`).value;
const match = promoVal.match(/[\d.]+/);
const pVal = match ? parseFloat(match[0]) : 0;
const recaudoElL = row.querySelector(`#r${i}l-${id}`);
const recaudoElP = row.querySelector(`#r${i}p-${id}`);
if (pVal > 0) {
// Local
const recaudoL = pVal - ((costProd + costFilm) * i + costAsesora + costDelivery + costPublicidad);
recaudoElL.innerHTML = `<span class="recaudo-label">LOCAL</span>S/ ${recaudoL.toFixed(2)}`;
recaudoElL.className = 'recaudo-v3 mb-1 ' + (recaudoL >= 0 ? 'text-success' : 'text-danger');
// Provincia
const recaudoP = pVal - ((costProd + costFilm) * i + costAsesoraP + costDeliveryP + costPublicidad);
recaudoElP.innerHTML = `<span class="recaudo-label">PROVINCIA</span>S/ ${recaudoP.toFixed(2)}`;
recaudoElP.className = 'recaudo-v3 bg-provincia ' + (recaudoP >= 0 ? 'text-success' : 'text-danger');
} else {
recaudoElL.innerHTML = `<span class="recaudo-label">LOCAL</span>-`;
recaudoElP.innerHTML = `<span class="recaudo-label">PROVINCIA</span>-`;
}
}
}
function eliminarRow(id) {
if (confirm('¿Eliminar este registro de la V3?')) {
window.location.href = 'delete_marketing_video_v3.php?id=' + id;
}
}
</script>
<?php include 'layout_footer.php'; ?>

View File

@ -0,0 +1,4 @@
-- Add provincia columns to marketing_costos_v3
ALTER TABLE marketing_costos_v3
ADD COLUMN comision_asesora_provincia DECIMAL(10,2) DEFAULT 0.00 AFTER comision_asesora,
ADD COLUMN delivery_provincia DECIMAL(10,2) DEFAULT 0.00 AFTER delivery;

View File

@ -0,0 +1,33 @@
<?php
session_start();
include 'db/config.php';
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
header('Location: login.php');
exit();
}
$db = db();
if (isset($_GET['id'])) {
$id = $_GET['id'];
// Obtener la ruta de la foto para borrarla
$stmt = $db->prepare("SELECT foto_producto FROM marketing_videos_v3 WHERE id = ?");
$stmt->execute([$id]);
$video = $stmt->fetch(PDO::FETCH_ASSOC);
if ($video) {
if (!empty($video['foto_producto']) && file_exists($video['foto_producto'])) {
unlink($video['foto_producto']);
}
// Borrar de ambas tablas V3
$db->prepare("DELETE FROM marketing_costos_v3 WHERE video_id = ?")->execute([$id]);
$db->prepare("DELETE FROM marketing_videos_v3 WHERE id = ?")->execute([$id]);
}
}
header("Location: calculo_costos_v3.php?success=deleted");
exit;
?>

View File

@ -256,10 +256,10 @@ $navItems = [
'text' => 'Producción de Video',
'roles' => ['Administrador', 'admin']
],
'calculo_costos' => [
'url' => 'calculo_costos_v2.php',
'calculo_costos_v3' => [
'url' => 'calculo_costos_v3.php',
'icon' => 'fa-calculator',
'text' => 'Cálculo de Costos',
'text' => 'Cálculo de Costos V3',
'roles' => ['Administrador', 'admin']
],
'marketing_assets' => [

View File

@ -0,0 +1,58 @@
<?php
include 'db/config.php';
session_start();
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
header('Location: login.php');
exit();
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$db = db();
$producto_id = !empty($_POST['producto_id']) ? $_POST['producto_id'] : null;
$orden = !empty($_POST['orden']) ? $_POST['orden'] : 0;
$costo_producto = !empty($_POST['costo_producto']) ? $_POST['costo_producto'] : 0;
$foto_path = null;
if (isset($_FILES['foto_producto']) && $_FILES['foto_producto']['error'] == 0) {
$target_dir = "assets/uploads/marketing_images/";
if (!is_dir($target_dir)) {
mkdir($target_dir, 0777, true);
}
$file_extension = pathinfo($_FILES["foto_producto"]["name"], PATHINFO_EXTENSION);
$file_name = uniqid() . '_v3.' . $file_extension;
$target_file = $target_dir . $file_name;
if (move_uploaded_file($_FILES["foto_producto"]["tmp_name"], $target_file)) {
$foto_path = $target_file;
}
}
try {
$stmt = $db->prepare("INSERT INTO marketing_videos_v3 (
producto_id,
foto_producto,
estado,
orden,
fecha_creacion
) VALUES (?, ?, 'PENDIENTE', ?, CURRENT_TIMESTAMP)");
$stmt->execute([
$producto_id,
$foto_path,
$orden
]);
$video_id = $db->lastInsertId();
// Guardar costo inicial en la tabla de costos V3
$stmt_costo = $db->prepare("INSERT INTO marketing_costos_v3 (video_id, costo_producto, inversion_total) VALUES (?, ?, ?)");
$stmt_costo->execute([$video_id, $costo_producto, $costo_producto]);
$redirect = $_POST['redirect'] ?? 'calculo_costos_v3.php?success=1';
header('Location: ' . $redirect);
} catch (PDOException $e) {
echo "Error: " . $e->getMessage();
}
}
?>

View File

@ -37,7 +37,7 @@ try {
$exists = $stmt->fetch();
if ($exists) {
$sql = "UPDATE marketing_costos SET $field = ? WHERE video_id = ?";
$sql = "UPDATE marketing_costos SET $field = ?, updated_at = CURRENT_TIMESTAMP WHERE video_id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$value, $video_id]);
} else {
@ -46,16 +46,18 @@ try {
$stmt->execute([$video_id, $value]);
}
// Recalculate inversion_total
// Recalculate inversion_total - Fetching fresh data after update
$stmt = $pdo->prepare("SELECT costo_producto, costo_fijo_film, comision_asesora, delivery, costo_publicitario FROM marketing_costos WHERE video_id = ?");
$stmt->execute([$video_id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$total = ($row['costo_producto'] ?? 0) +
($row['costo_fijo_film'] ?? 0) +
($row['comision_asesora'] ?? 0) +
($row['delivery'] ?? 0) +
($row['costo_publicitario'] ?? 0);
$costo_prod = floatval($row['costo_producto'] ?? 0);
$costo_film = floatval($row['costo_fijo_film'] ?? 0);
$costo_asesora = floatval($row['comision_asesora'] ?? 0);
$costo_delivery = floatval($row['delivery'] ?? 0);
$costo_pub = floatval($row['costo_publicitario'] ?? 0);
$total = $costo_prod + $costo_film + $costo_asesora + $costo_delivery + $costo_pub;
$stmt = $pdo->prepare("UPDATE marketing_costos SET inversion_total = ? WHERE video_id = ?");
$stmt->execute([$total, $video_id]);

View File

@ -0,0 +1,76 @@
<?php
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
http_response_code(403);
echo json_encode(['error' => 'Acceso no autorizado.']);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
if (!$data || !isset($data['id']) || !isset($data['field']) || !isset($data['value'])) {
http_response_code(400);
echo json_encode(['error' => 'Datos incompletos.']);
exit;
}
$video_id = $data['id'];
$field = $data['field'];
$value = $data['value'];
// Campos permitidos
$allowed_fields = [
'costo_producto', 'costo_fijo_film', 'comision_asesora', 'delivery',
'costo_publicitario', 'promo_1', 'promo_2', 'promo_3',
'comision_asesora_provincia', 'delivery_provincia'
];
if (!in_array($field, $allowed_fields)) {
http_response_code(400);
echo json_encode(['error' => 'Campo no permitido.']);
exit;
}
try {
$pdo = db();
// Verificar si existe el registro en costos V3
$stmt = $pdo->prepare("SELECT id FROM marketing_costos_v3 WHERE video_id = ?");
$stmt->execute([$video_id]);
$exists = $stmt->fetch();
if ($exists) {
$sql = "UPDATE marketing_costos_v3 SET $field = ?, updated_at = CURRENT_TIMESTAMP WHERE video_id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$value, $video_id]);
} else {
$sql = "INSERT INTO marketing_costos_v3 (video_id, $field) VALUES (?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$video_id, $value]);
}
// Recalcular inversion_total (Local)
$stmt = $pdo->prepare("SELECT costo_producto, costo_fijo_film, comision_asesora, delivery, costo_publicitario FROM marketing_costos_v3 WHERE video_id = ?");
$stmt->execute([$video_id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$total = floatval($row['costo_producto'] ?? 0) +
floatval($row['costo_fijo_film'] ?? 0) +
floatval($row['comision_asesora'] ?? 0) +
floatval($row['delivery'] ?? 0) +
floatval($row['costo_publicitario'] ?? 0);
$stmt = $pdo->prepare("UPDATE marketing_costos_v3 SET inversion_total = ? WHERE video_id = ?");
$stmt->execute([$total, $video_id]);
echo json_encode([
'success' => true,
'message' => 'Actualizado en V3',
'new_total' => $total
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Error: ' . $e->getMessage()]);
}
?>