Autosave: 20260602-014831

This commit is contained in:
Flatlogic Bot 2026-06-02 01:48:20 +00:00
parent f244f447ec
commit 923ddb7b18
5 changed files with 340 additions and 35 deletions

View File

@ -20,22 +20,80 @@ function splitProvinciaDistrito(?string $value): array
$clean = preg_replace('/\s+/', ' ', $value);
$parts = preg_split('/\s*[\/\-,|]\s*/', $clean, 2);
if (is_array($parts) && count($parts) === 2) {
if (is_array($parts) and count($parts) === 2) {
return [trim($parts[0]), trim($parts[1])];
}
return [$clean, ''];
}
function parseTotalQuantity(mixed $value): int
{
if (is_int($value)) {
return $value;
}
if (is_float($value)) {
return (int)$value;
}
$s = trim((string)$value);
if ($s === '') {
return 0;
}
// Old/legacy format can be: "1+2+3"
if (strpos($s, '+') !== false) {
$sum = 0;
foreach (explode('+', $s) as $part) {
$part = trim($part);
if ($part === '') {
continue;
}
if (is_numeric($part)) {
$sum += (int)$part;
continue;
}
if (preg_match('/\d+/', $part, $m)) {
$sum += (int)$m[0];
}
}
return $sum;
}
if (is_numeric($s)) {
return (int)$s;
}
if (preg_match('/\d+/', $s, $m)) {
return (int)$m[0];
}
return 0;
}
function formatPriceSegment(float $value): string
{
$rounded = round($value, 2);
if (abs($rounded - (int)$rounded) < 0.00001) {
return (string)(int)$rounded;
}
// Keep up to 2 decimals, but remove trailing zeros
$s = number_format($rounded, 2, '.', '');
$s = rtrim(rtrim($s, '0'), '.');
return $s;
}
function extractProductNames(array $pedido): array
{
$names = [];
$notas = (string)($pedido['notas'] ?? '');
if (preg_match('/Detalle de productos:\s*(.+)$/mi', $notas, $match)) {
if (preg_match('/Detalle de productos:\\s*(.+)$/mi', $notas, $match)) {
$items = explode(',', $match[1]);
foreach ($items as $item) {
$name = preg_replace('/\s*\(x\d+\)\s*$/i', '', trim($item));
$name = preg_replace('/\\s*\\(x\\d+\\)\\s*$/i', '', trim($item));
if ($name !== '') {
$names[] = $name;
}
@ -54,6 +112,115 @@ function extractProductNames(array $pedido): array
return array_values(array_unique($names));
}
function extractProductDetailsWithQuantities(array $pedido): array
{
$notas = (string)($pedido['notas'] ?? '');
// Prefer parsing from "Detalle de productos:" (it stores quantities as (xN))
if (preg_match_all('/Detalle de productos:\\s*(.+)$/mi', $notas, $matches) && !empty($matches[1])) {
$lastLine = trim((string)end($matches[1]));
if ($lastLine !== '') {
$parts = preg_split('/\\s*,\\s*/u', $lastLine);
$out = [];
if (is_array($parts)) {
foreach ($parts as $part) {
$part = trim((string)$part);
if ($part === '') {
continue;
}
// Expected format: "Nombre de producto (x2)"
if (preg_match('/^(.*?)\\s*\\(x\\s*(\\d+)\\s*\\)\\s*$/iu', $part, $m)) {
$name = trim((string)$m[1]);
$qty = (int)$m[2];
} else {
$name = $part;
$qty = 1;
}
if ($name !== '') {
if ($qty <= 0) {
$qty = 1;
}
$out[] = ['name' => $name, 'qty' => $qty];
}
}
}
if (!empty($out)) {
return $out;
}
}
}
// Fallback: use "producto" + "cantidad" (best-effort)
$productoStr = (string)($pedido['producto'] ?? '');
$names = [];
foreach (preg_split('/\\s*,\\s*/u', $productoStr) as $n) {
$n = trim((string)$n);
if ($n !== '') {
$names[] = $n;
}
}
if (empty($names)) {
return [];
}
$cantidad_total = parseTotalQuantity($pedido['cantidad'] ?? 0);
// Legacy possibility: "cantidad" can be like "1+2"
$cantidadField = $pedido['cantidad'] ?? '';
$qtyParts = [];
if (is_string($cantidadField) && strpos(trim($cantidadField), '+') !== false) {
$qtyParts = array_map('trim', explode('+', $cantidadField));
}
$out = [];
$countNames = count($names);
foreach ($names as $i => $name) {
if ($countNames === 1) {
$qty = $cantidad_total > 0 ? $cantidad_total : 1;
} elseif (!empty($qtyParts) && isset($qtyParts[$i]) && is_numeric($qtyParts[$i])) {
$qty = (int)$qtyParts[$i];
if ($qty <= 0) {
$qty = 1;
}
} else {
$qty = 1;
}
$out[] = ['name' => $name, 'qty' => $qty];
}
return $out;
}
function getEanMap(PDO $pdo, array $productNames): array
{
$productNames = array_values(array_filter(array_map('trim', $productNames)));
if (empty($productNames)) {
return [];
}
$uniqueNames = array_values(array_unique($productNames));
$placeholders = implode(',', array_fill(0, count($uniqueNames), '?'));
$stmt = $pdo->prepare("SELECT nombre, ean FROM products WHERE nombre IN ($placeholders)");
$stmt->execute($uniqueNames);
$map = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$nombre = (string)($row['nombre'] ?? '');
$ean = trim((string)($row['ean'] ?? ''));
if ($nombre !== '' && $ean !== '') {
$map[$nombre] = $ean;
}
}
return $map;
}
function getEansForProducts(PDO $pdo, array $productNames): string
{
if (empty($productNames)) {
@ -68,7 +235,7 @@ function getEansForProducts(PDO $pdo, array $productNames): string
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$ean = trim((string)($row['ean'] ?? ''));
if ($ean !== '') {
$map[$row['nombre']] = $ean;
$map[(string)($row['nombre'] ?? '')] = $ean;
}
}
@ -79,7 +246,7 @@ function getEansForProducts(PDO $pdo, array $productNames): string
}
}
return implode(' | ', array_values(array_unique($eans)));
return implode('|', array_values(array_unique($eans)));
}
try {
@ -88,10 +255,11 @@ try {
$user_id = $_SESSION['user_id'];
$user_role = $_SESSION['user_role'] ?? 'Asesor';
$selected_month = $_GET['mes'] ?? '';
$selected_year = $_GET['año'] ?? '';
$search_query = trim($_GET['q'] ?? '');
// Exportar solo los pedidos con fecha de entrega "mañana"
$date_condition = "DATE(p.fecha_entrega) = DATE_ADD(CURDATE(), INTERVAL 1 DAY)";
$sql = "SELECT p.* FROM pedidos p WHERE p.estado IN ('RUTA_CONTRAENTREGA', 'PENDIENTE', 'NO CONTESTO, VOLVER A LLAMAR', 'NO CONTESTO, DEVOLVER LLAMADA', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA', 'RETORNADO')";
$params = [];
@ -107,15 +275,8 @@ try {
$params[] = "%$search_query%";
}
if ($selected_month !== '') {
$sql .= " AND MONTH(p.created_at) = ?";
$params[] = $selected_month;
}
if ($selected_year !== '') {
$sql .= " AND YEAR(p.created_at) = ?";
$params[] = $selected_year;
}
// Siempre: pedidos cuya fecha de entrega sea mañana
$sql .= " AND $date_condition";
$sql .= " ORDER BY p.created_at DESC";
$stmt = $pdo->prepare($sql);
@ -141,10 +302,68 @@ try {
foreach ($pedidos as $pedido) {
[$provincia, $distrito] = splitProvinciaDistrito($pedido['codigo_rastreo'] ?? '');
$cantidad = (int)($pedido['cantidad'] ?? 0);
$cantidad_total = parseTotalQuantity($pedido['cantidad'] ?? 0);
$total = (float)($pedido['monto_total'] ?? 0);
$precio = $cantidad > 0 ? round($total / $cantidad, 2) : 0;
$ean = getEansForProducts($pdo, extractProductNames($pedido));
$unit_price_rounded = $cantidad_total > 0 ? round($total / $cantidad_total, 2) : 0;
$eanOut = '';
$cantidadOut = $cantidad_total;
$precioOut = $unit_price_rounded;
$details = extractProductDetailsWithQuantities($pedido);
$eanSegments = [];
$qtySegments = [];
$priceSegments = [];
if (!empty($details)) {
$names = [];
foreach ($details as $d) {
if (!empty($d['name'])) {
$names[] = (string)$d['name'];
}
}
$eanMap = getEanMap($pdo, $names);
foreach ($details as $d) {
$name = (string)($d['name'] ?? '');
if ($name === '') {
continue;
}
$qty = (int)($d['qty'] ?? 0);
if ($qty <= 0) {
$qty = 1;
}
$ean = trim((string)($eanMap[$name] ?? ''));
if ($ean === '') {
continue;
}
$eanSegments[] = $ean;
$qtySegments[] = $qty;
$priceSegments[] = formatPriceSegment((float)$unit_price_rounded); // unit price repeated
}
}
// If we have segment data, format with pipes (no spaces)
if (count($eanSegments) > 1) {
$eanOut = implode('|', $eanSegments);
$cantidadOut = implode('|', $qtySegments);
$precioOut = implode('|', $priceSegments);
} elseif (count($eanSegments) === 1) {
$eanOut = $eanSegments[0];
$cantidadOut = $qtySegments[0] ?? $cantidad_total;
$precioOut = $unit_price_rounded;
} else {
// Fallback (keep old behavior, but with "|" formatting)
$eanOut = getEansForProducts($pdo, extractProductNames($pedido));
$cantidadOut = $cantidad_total;
$precioOut = $cantidad_total > 0 ? round($total / $cantidad_total, 2) : 0;
}
$rows[] = [
(string)($pedido['nombre_completo'] ?? ''),
@ -156,13 +375,27 @@ try {
(string)($pedido['direccion_exacta'] ?? ''),
(string)($pedido['referencia_domicilio'] ?? ''),
(string)($pedido['coordenadas'] ?? ''),
(string)$ean,
$cantidad,
$precio,
(string)$eanOut,
$cantidadOut,
$precioOut,
$total
];
}
// Right-align certain columns (Codigo EAN, Cantidad, Precio, Total) in the exported Excel
$rightAlignedCols = [9, 10, 11, 12];
foreach ($rows as $rIdx => &$row) {
foreach ($rightAlignedCols as $cIdx) {
if (!array_key_exists($cIdx, $row)) {
continue;
}
if (is_string($row[$cIdx]) && strpos($row[$cIdx], '<right>') !== false) {
continue;
}
$row[$cIdx] = '<right>' . (string)$row[$cIdx] . '</right>';
}
}
unset($row);
$filename = 'ruta_contraentrega_' . date('Y-m-d_H-i') . '.xlsx';
SimpleXLSXGen::fromArray($rows, 'Ruta Contraentrega')->downloadAs($filename);
exit;

View File

@ -165,6 +165,8 @@ include 'layout_header.php';
<?php if ($user_role === 'Administrador' || $user_role === 'Logistica'): ?>
<a href="imprimir_rotulos_grandes.php?<?php echo http_build_query($_GET); ?>" class="btn btn-dark" target="_blank">Generar Todos los Rótulos</a>
<a href="download_report.php" class="btn btn-success">Descargar Todos los Rotulados</a>
<?php endif; ?>
<?php if (in_array($user_role, ['Administrador', 'admin', 'Logistica', 'Control Logistico'], true)): ?>
<a href="download_shalom.php?type=terrestre" class="btn btn-primary">Descargar Shalom Terrestre</a>
<a href="download_shalom.php?type=aereo" class="btn btn-warning" style="background-color: #fd7e14; border-color: #fd7e14; color: white;">Descargar Shalom Aéreo</a>
<?php endif; ?>

View File

@ -30,6 +30,7 @@ $pedido = [
'fecha_recojo' => '',
'asesor_id' => $user_id, // Default to current user
'notas' => '',
'nota_adicional' => '',
];
$provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento();
@ -347,6 +348,11 @@ include 'layout_header.php';
<textarea class="form-control" id="notas" name="notas" rows="3"><?php echo htmlspecialchars($pedido['notas']); ?></textarea>
</div>
<div class="mb-3">
<label for="nota_adicional" class="form-label">COLOCAR AQUI (DEDICATORIA O GRABADO), OBSERVACION DEL CLIENTE</label>
<textarea class="form-control" id="nota_adicional" name="nota_adicional" rows="2" placeholder="Ej.: Solicita dedicatoria para hija María, Ej.: Desea antes de las 4PM"><?php echo htmlspecialchars($pedido['nota_adicional'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Guardar Pedido Contraentrega</button>
<a href="<?php echo htmlspecialchars($_SERVER['HTTP_REFERER'] ?? 'pedidos.php'); ?>" class="btn btn-secondary">Cancelar</a>
</form>

View File

@ -102,6 +102,35 @@ $selected_month = $_GET['mes'] ?? '';
$selected_year = $_GET['año'] ?? '';
$search_query = $_GET['q'] ?? '';
$period = $_GET['period'] ?? '';
$date_condition = '';
$label_period = '';
if (!empty($period)) {
switch ($period) {
case 'today':
$date_condition = "DATE(p.created_at) = CURDATE()";
$label_period = "Hoy";
break;
case 'yesterday':
$date_condition = "DATE(p.created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)";
$label_period = "Ayer";
break;
case '7':
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)";
$label_period = "Últimos 7 días";
break;
case '15':
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 15 DAY)";
$label_period = "Últimos 15 días";
break;
case 'month':
$date_condition = "MONTH(p.created_at) = MONTH(CURDATE()) AND YEAR(p.created_at) = YEAR(CURDATE())";
$label_period = "Este Mes";
break;
}
}
$sql = "SELECT p.*, u.nombre_asesor as asesor_nombre FROM pedidos p LEFT JOIN users u ON p.asesor_id = u.id WHERE p.estado IN ('RUTA_CONTRAENTREGA', 'PENDIENTE', 'NO CONTESTO, VOLVER A LLAMAR', 'NO CONTESTO, DEVOLVER LLAMADA', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA', 'RETORNADO')";
$params = [];
@ -117,13 +146,17 @@ if (!empty($search_query)) {
$params[] = "%$search_query%";
}
if (!empty($selected_month)) {
$sql .= " AND MONTH(p.created_at) = ?";
$params[] = $selected_month;
}
if (!empty($selected_year)) {
$sql .= " AND YEAR(p.created_at) = ?";
$params[] = $selected_year;
if (!empty($date_condition)) {
$sql .= " AND $date_condition";
} else {
if (!empty($selected_month)) {
$sql .= " AND MONTH(p.created_at) = ?";
$params[] = $selected_month;
}
if (!empty($selected_year)) {
$sql .= " AND YEAR(p.created_at) = ?";
$params[] = $selected_year;
}
}
$sql .= " ORDER BY p.created_at DESC";
@ -237,7 +270,20 @@ include 'layout_header.php';
<?php endforeach; ?>
</select>
</div>
<div class="col-auto mt-4">
<?php
$qParam = !empty($search_query) ? '&q=' . urlencode((string)$search_query) : '';
?>
<div class="col-12">
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="text-muted fw-semibold me-2">Fecha:</span>
<a href="ruta_contraentrega.php?period=today<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == 'today' ? 'active' : ''; ?>">Hoy</a>
<a href="ruta_contraentrega.php?period=yesterday<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == 'yesterday' ? 'active' : ''; ?>">Ayer</a>
<a href="ruta_contraentrega.php?period=7<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == '7' ? 'active' : ''; ?>">7 Días</a>
<a href="ruta_contraentrega.php?period=15<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == '15' ? 'active' : ''; ?>">15 Días</a>
<a href="ruta_contraentrega.php?period=month<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == 'month' ? 'active' : ''; ?>">Este Mes</a>
</div>
</div>
<div class="col-auto mt-2">
<button type="submit" class="btn btn-info">Filtrar</button>
<a href="ruta_contraentrega.php" class="btn btn-secondary">Limpiar</a>
<?php if (in_array($user_role, ['Administrador', 'admin', 'Asesor'], true)): ?>
@ -248,15 +294,13 @@ include 'layout_header.php';
<?php
$excelParams = array_filter([
'q' => $search_query,
'mes' => $selected_month,
'año' => $selected_year,
], static function ($value) {
return $value !== '' && $value !== null;
});
$excelUrl = 'download_ruta_contraentrega.php' . (!empty($excelParams) ? '?' . http_build_query($excelParams) : '');
?>
<a href="<?php echo htmlspecialchars($excelUrl); ?>" class="btn btn-success">
<i class="fas fa-file-excel me-1"></i> Descargar Excel
<i class="fas fa-file-excel me-1"></i> Descargar Excel (Mañana)
</a>
</div>
</form>
@ -283,6 +327,7 @@ include 'layout_header.php';
<th style="width: 140px;">Distrito</th>
<th style="width: 150px;">Coordenadas</th>
<th style="width: 200px;">Producto</th>
<th style="width: 320px;">Dedicatoria / Obs.</th>
<th style="width: 80px;">Cant.</th>
<?php if ($user_role !== 'Logistica'): ?>
<th style="width: 100px;">Total</th>
@ -354,6 +399,16 @@ include 'layout_header.php';
<td><?php echo htmlspecialchars($distritoPedido); ?></td>
<td><?php echo htmlspecialchars($pedido['coordenadas'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($pedido['producto']); ?></td>
<?php
$nota_adicional = trim((string)($pedido['nota_adicional'] ?? ''));
if ($nota_adicional === '') {
$nota_adicional = trim((string)($pedido['descargo'] ?? ''));
}
$nota_adicional_display = $nota_adicional !== '' ? $nota_adicional : 'N/A';
?>
<td style="max-width: 320px; white-space: normal; word-break: break-word;">
<?php echo htmlspecialchars($nota_adicional_display); ?>
</td>
<td><?php echo htmlspecialchars($pedido['cantidad']); ?></td>
<?php if ($user_role !== 'Logistica'): ?>
<td><?php echo htmlspecialchars($pedido['monto_total']); ?></td>

View File

@ -100,6 +100,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$numero_operacion = trim($_POST['numero_operacion'] ?? '');
$banco = trim($_POST['banco'] ?? '');
$notas = trim($_POST['notas'] ?? '');
$nota_adicional = trim($_POST['nota_adicional'] ?? '');
// Campo combinado del formulario: (Dedicatoria o grabado) + Observación del cliente
// Guardamos en ambas columnas para mantener compatibilidad con el resto del sistema.
$descargo = $nota_adicional;
// Normalizar Paquete: el selector "Seleccionar" llega como cadena vacía,
// pero la columna ENUM permite NULL y no permite ''.
@ -175,6 +180,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'banco' => $banco,
'monto_debe' => $monto_debe,
'notas' => $notas,
'nota_adicional' => $nota_adicional,
'descargo' => $descargo,
'seguimiento' => $seguimiento,
'fecha_entrega' => $fecha_entrega,
'tipo_paquete' => $tipo_paquete,
@ -204,6 +211,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
"banco = :banco",
"monto_debe = :monto_debe",
"notas = :notas",
"nota_adicional = :nota_adicional",
"descargo = :descargo",
"seguimiento = :seguimiento",
"fecha_entrega = :fecha_entrega",
"estado = :estado",
@ -227,8 +236,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$params['asesor_id'] = $_SESSION['user_id'];
$params['estado'] = $_POST['estado'] ?? 'RUTA_CONTRAENTREGA';
$columns_sql = "dni_cliente, nombre_completo, celular, agencia, sede_envio, direccion_exacta, referencia_domicilio, coordenadas, codigo_rastreo, codigo_tracking, producto, cantidad, monto_total, monto_adelantado, numero_operacion, banco, monto_debe, asesor_id, notas, estado, seguimiento, fecha_entrega, tipo_paquete";
$values_sql = ":dni_cliente, :nombre_completo, :celular, :agencia, :sede_envio, :direccion_exacta, :referencia_domicilio, :coordenadas, :codigo_rastreo, :codigo_tracking, :producto, :cantidad, :monto_total, :monto_adelantado, :numero_operacion, :banco, :monto_debe, :asesor_id, :notas, :estado, :seguimiento, :fecha_entrega, :tipo_paquete";
$columns_sql = "dni_cliente, nombre_completo, celular, agencia, sede_envio, direccion_exacta, referencia_domicilio, coordenadas, codigo_rastreo, codigo_tracking, producto, cantidad, monto_total, monto_adelantado, numero_operacion, banco, monto_debe, asesor_id, notas, nota_adicional, descargo, estado, seguimiento, fecha_entrega, tipo_paquete";
$values_sql = ":dni_cliente, :nombre_completo, :celular, :agencia, :sede_envio, :direccion_exacta, :referencia_domicilio, :coordenadas, :codigo_rastreo, :codigo_tracking, :producto, :cantidad, :monto_total, :monto_adelantado, :numero_operacion, :banco, :monto_debe, :asesor_id, :notas, :nota_adicional, :descargo, :estado, :seguimiento, :fecha_entrega, :tipo_paquete";
$sql = "INSERT INTO pedidos ($columns_sql) VALUES ($values_sql)";
$stmt = $pdo->prepare($sql);