Autosave: 20260810-194336

This commit is contained in:
Flatlogic Bot 2026-08-10 19:41:44 +00:00
parent a225a6809d
commit 13e46e1545
7 changed files with 222 additions and 168 deletions

View File

@ -6,6 +6,7 @@ if (!isset($_SESSION['user_id'])) {
}
require_once 'db/config.php';
require_once 'includes/callcenter_test_helpers.php';
require_once 'vendor/autoload.php';
use Shuchkin\SimpleXLSXGen;
@ -132,86 +133,18 @@ function extractProductNames(array $pedido): array
return array_values(array_unique($names));
}
function extractProductDetailsWithQuantities(array $pedido): array
function extractProductDetailsWithQuantities(PDO $pdo, 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));
}
$rows = cc_test_resolve_pedido_product_rows($pdo, $pedido, 'ruta_contraentrega_pedido_id');
$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;
foreach ($rows as $row) {
$name = trim((string)($row['nombre'] ?? ''));
$qty = (int)($row['cantidad'] ?? 0);
if ($name === '') {
continue;
}
$out[] = ['name' => $name, 'qty' => $qty];
$out[] = ['name' => $name, 'qty' => $qty > 0 ? $qty : 1];
}
return $out;
@ -365,7 +298,7 @@ $is_admin = in_array($user_role, ['Administrador', 'admin'], true);
$cantidadOut = $cantidad_total;
$precioOut = $unit_price_rounded;
$details = extractProductDetailsWithQuantities($pedido);
$details = extractProductDetailsWithQuantities($pdo, $pedido);
$eanSegments = [];
$qtySegments = [];

View File

@ -1486,6 +1486,7 @@ require_once 'layout_header.php';
color: var(--cc-assessor-accent-contrast, #212529);
border-color: var(--cc-assessor-accent, #adb5bd) !important;
font-weight: 600;
white-space: nowrap;
}
.cc-callcenter-color-trigger {
@ -1849,6 +1850,7 @@ require_once 'layout_header.php';
<?php if ($activeAssessorSummaryCount > 0): ?>
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3">
<?php foreach ($activeAssessorSummaryCards as $assessorCard): ?>
<?php $pendingCount = (int) ($assessorCard['pending'] ?? 0); ?>
<div class="col">
<div class="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="card-body">
@ -1857,7 +1859,9 @@ require_once 'layout_header.php';
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
</div>
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
<?php if ($pendingCount > 0): ?>
<span class="badge rounded-pill border cc-callcenter-assessor-badge text-nowrap">Pendientes: <?php echo $pendingCount; ?></span>
<?php endif; ?>
</div>
<div class="mt-2">
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
@ -1903,6 +1907,7 @@ require_once 'layout_header.php';
<summary class="cc-callcenter-details-summary">Ver <?php echo (int) $inactiveAssessorSummaryCount; ?> asesora<?php echo $inactiveAssessorSummaryCount === 1 ? '' : 's'; ?> sin actividad</summary>
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3 mt-2">
<?php foreach ($inactiveAssessorSummaryCards as $assessorCard): ?>
<?php $pendingCount = (int) ($assessorCard['pending'] ?? 0); ?>
<div class="col">
<div class="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card is-inactive" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
<div class="card-body">
@ -1911,7 +1916,9 @@ require_once 'layout_header.php';
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
</div>
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
<?php if ($pendingCount > 0): ?>
<span class="badge rounded-pill border cc-callcenter-assessor-badge text-nowrap">Pendientes: <?php echo $pendingCount; ?></span>
<?php endif; ?>
</div>
<div class="mt-2">
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>

View File

@ -6,6 +6,7 @@ if (!isset($_SESSION['user_id'])) {
}
require_once 'db/config.php';
require_once 'includes/callcenter_test_helpers.php';
$pdo = db();
// Fetch all products to have their prefixes ready
@ -203,39 +204,21 @@ $label_generated_date = date('d/m');
<?php foreach ($pedidos as $pedido):
$display_variants = [];
$found_in_notes = false;
// Try to parse from notas first
if (preg_match_all('/Detalle de productos: (.*)$/m', $pedido['notas'], $matches)) {
$last_match = end($matches[1]);
$details = explode(', ', $last_match);
foreach ($details as $detail) {
if (preg_match('/(.*) \(x(\d+)\)/', $detail, $d_matches)) {
$p_name = trim($d_matches[1]);
$p_qty = (int)$d_matches[2];
$prefix = $product_prefixes[$p_name] ?? 'S/V';
$display_variants[] = "($p_qty) $prefix";
$found_in_notes = true;
}
$display_rows = cc_test_resolve_pedido_product_rows($pdo, $pedido, 'ruta_contraentrega_pedido_id');
foreach ($display_rows as $row) {
$p_name = trim((string)($row['nombre'] ?? ''));
if ($p_name === '') {
continue;
}
}
// Fallback if not found in notes
if (!$found_in_notes && !empty($pedido['producto'])) {
$names = explode(', ', $pedido['producto']);
$total_qty = (int)($pedido['cantidad'] ?: 1);
if (count($names) === 1) {
$prefix = $product_prefixes[trim($names[0])] ?? 'S/V';
$display_variants[] = "($total_qty) $prefix";
} else {
foreach ($names as $name) {
$prefix = $product_prefixes[trim($name)] ?? 'S/V';
$display_variants[] = "(1) $prefix";
}
$p_qty = (int)($row['cantidad'] ?? 0);
if ($p_qty <= 0) {
$p_qty = 1;
}
$prefix = $product_prefixes[$p_name] ?? 'S/V';
$display_variants[] = "($p_qty) $prefix";
}
$variante_text = !empty($display_variants) ? implode(' + ', $display_variants) : 'S/V';
// Nota adicional (dedicatorias/observaciones): mostrar en tamaño pequeño solo la primera palabra

View File

@ -3159,6 +3159,184 @@ function cc_test_fetch_shalom_sedes(PDO $pdo): array
return is_array($sedes) ? array_values(array_filter(array_map('strval', $sedes), static fn ($value) => trim($value) !== '')) : [];
}
function cc_test_parse_quantity_value(mixed $value, int $default = 1): int
{
if (is_int($value)) {
return $value > 0 ? $value : $default;
}
if (is_float($value)) {
$quantity = (int) round($value);
return $quantity > 0 ? $quantity : $default;
}
$value = trim((string) $value);
if ($value === '') {
return $default;
}
if (strpos($value, '+') !== false) {
$sum = 0;
foreach (explode('+', $value) as $part) {
$part = trim($part);
if ($part === '') {
continue;
}
if (is_numeric($part)) {
$sum += (int) $part;
continue;
}
if (preg_match('/\d+/', $part, $match)) {
$sum += (int) $match[0];
}
}
return $sum > 0 ? $sum : $default;
}
if (preg_match('/\d+/', $value, $match)) {
$quantity = (int) $match[0];
return $quantity > 0 ? $quantity : $default;
}
return $default;
}
function cc_test_parse_product_detail_rows_from_text(string $text): array
{
$text = trim($text);
if ($text === '') {
return [];
}
if (!preg_match_all('/(?:Detalle de productos|Detalle confirmado):\s*(.+)$/mi', $text, $matches) || empty($matches[1])) {
return [];
}
$lastLine = trim((string) end($matches[1]));
if ($lastLine === '') {
return [];
}
$parts = preg_split('/\s*,\s*/u', $lastLine);
if (!is_array($parts)) {
return [];
}
$rows = [];
foreach ($parts as $part) {
$part = trim((string) $part);
if ($part === '') {
continue;
}
$name = $part;
$quantity = 1;
if (preg_match('/^(.*?)\s*\(x\s*(\d+)\s*\)$/iu', $part, $match)) {
$name = trim((string) $match[1]);
$quantity = (int) $match[2];
}
$name = trim($name);
if ($name === '') {
continue;
}
$rows[] = [
'nombre' => $name,
'cantidad' => $quantity > 0 ? $quantity : 1,
];
}
return $rows;
}
function cc_test_product_rows_from_tracking(array $tracking): array
{
$rows = [];
$mainName = trim((string) ($tracking['confirmacion_producto'] ?? ''));
$mainQty = cc_test_parse_quantity_value($tracking['confirmacion_cantidad'] ?? '', 1);
if ($mainName !== '') {
$rows[] = [
'nombre' => $mainName,
'cantidad' => $mainQty,
];
}
$extraName = trim((string) ($tracking['confirmacion_producto_extra'] ?? ''));
$extraQty = cc_test_parse_quantity_value($tracking['confirmacion_cantidad_extra'] ?? '', 1);
if ($extraName !== '') {
$rows[] = [
'nombre' => $extraName,
'cantidad' => $extraQty,
];
}
return $rows;
}
function cc_test_resolve_pedido_product_rows(PDO $pdo, array $pedido, ?string $trackingLinkColumn = null): array
{
$pedidoId = (int) ($pedido['id'] ?? 0);
$allowedTrackingColumns = ['ruta_contraentrega_pedido_id', 'pedido_rotulado_pedido_id'];
if ($pedidoId > 0 && $trackingLinkColumn !== null && in_array($trackingLinkColumn, $allowedTrackingColumns, true) && cc_test_table_exists($pdo, 'callcenter_test_tracking')) {
$sql = 'SELECT confirmacion_producto, confirmacion_cantidad, confirmacion_producto_extra, confirmacion_cantidad_extra FROM callcenter_test_tracking WHERE ' . $trackingLinkColumn . ' = ? ORDER BY id DESC LIMIT 1';
$stmt = $pdo->prepare($sql);
$stmt->execute([$pedidoId]);
$tracking = $stmt->fetch(PDO::FETCH_ASSOC);
if (is_array($tracking)) {
$rows = cc_test_product_rows_from_tracking($tracking);
if (!empty($rows)) {
return $rows;
}
}
}
$rows = cc_test_parse_product_detail_rows_from_text((string) ($pedido['notas'] ?? ''));
if (!empty($rows)) {
return $rows;
}
$producto = trim((string) ($pedido['producto'] ?? ''));
if ($producto === '') {
return [];
}
$names = array_values(array_filter(array_map('trim', preg_split('/\s*,\s*/u', $producto) ?: []), static fn ($value) => $value !== ''));
if (empty($names)) {
return [];
}
$cantidadTotal = cc_test_parse_quantity_value($pedido['cantidad'] ?? 1, 1);
$cantidadField = $pedido['cantidad'] ?? '';
$qtyParts = [];
if (is_string($cantidadField) && strpos(trim($cantidadField), '+') !== false) {
$qtyParts = array_values(array_map('trim', explode('+', trim($cantidadField))));
}
$rows = [];
$nameCount = count($names);
foreach ($names as $index => $name) {
if ($nameCount === 1) {
$quantity = $cantidadTotal;
} elseif (!empty($qtyParts) && isset($qtyParts[$index]) && is_numeric($qtyParts[$index])) {
$quantity = (int) $qtyParts[$index];
} else {
$quantity = 1;
}
$rows[] = [
'nombre' => $name,
'cantidad' => $quantity > 0 ? $quantity : 1,
];
}
return $rows;
}
function cc_test_row_background_style(string $estado): string
{
$color = match (cc_test_normalize_state($estado)) {

View File

@ -6,6 +6,7 @@ if (!isset($_SESSION['user_id'])) {
}
require_once 'db/config.php';
require_once 'includes/callcenter_test_helpers.php';
$pdo = db();
$user_id = $_SESSION['user_id'];
@ -77,34 +78,9 @@ $stmt_products = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre AS
$products = $stmt_products->fetchAll();
// Parse products for editing
$display_products = [];
if (!empty($pedido['id'])) {
// Try to parse from notas first as it has quantities
if (preg_match_all('/Detalle de productos: (.*)$/m', $pedido['notas'], $matches)) {
// Take the last match
$last_match = end($matches[1]);
$details = explode(', ', $last_match);
foreach ($details as $detail) {
if (preg_match('/(.*) \(x(\d+)\)/', $detail, $d_matches)) {
$display_products[] = [
'nombre' => trim($d_matches[1]),
'cantidad' => (int)$d_matches[2]
];
}
}
}
// Fallback if parsing failed or no details in notas
if (empty($display_products) && !empty($pedido['producto'])) {
$names = explode(', ', $pedido['producto']);
foreach ($names as $name) {
$display_products[] = [
'nombre' => trim($name),
'cantidad' => count($names) == 1 ? $pedido['cantidad'] : 1
];
}
}
}
$display_products = !empty($pedido['id'])
? cc_test_resolve_pedido_product_rows($pdo, $pedido, 'pedido_rotulado_pedido_id')
: [];
if (empty($display_products)) {
$display_products[] = ['nombre' => '', 'cantidad' => 1];

View File

@ -7,6 +7,7 @@ if (!isset($_SESSION['user_id'])) {
require_once 'db/config.php';
require_once 'includes/contraentrega_cobertura.php';
require_once 'includes/callcenter_test_helpers.php';
require_once 'includes/tipo_paquete.php';
$pdo = db();
ensureTipoPaqueteEnumDefinition($pdo);
@ -79,34 +80,9 @@ $stmt_products = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre AS
$products = $stmt_products->fetchAll();
// Parse products for editing
$display_products = [];
if (!empty($pedido['id'])) {
// Try to parse from notas first as it has quantities
if (preg_match_all('/Detalle de productos: (.*)$/m', $pedido['notas'], $matches)) {
// Take the last match
$last_match = end($matches[1]);
$details = explode(', ', $last_match);
foreach ($details as $detail) {
if (preg_match('/(.*) \(x(\d+)\)/', $detail, $d_matches)) {
$display_products[] = [
'nombre' => trim($d_matches[1]),
'cantidad' => (int)$d_matches[2]
];
}
}
}
// Fallback if parsing failed or no details in notas
if (empty($display_products) && !empty($pedido['producto'])) {
$names = explode(', ', $pedido['producto']);
foreach ($names as $name) {
$display_products[] = [
'nombre' => trim($name),
'cantidad' => count($names) == 1 ? $pedido['cantidad'] : 1
];
}
}
}
$display_products = !empty($pedido['id'])
? cc_test_resolve_pedido_product_rows($pdo, $pedido, 'ruta_contraentrega_pedido_id')
: [];
if (empty($display_products)) {
$display_products[] = ['nombre' => '', 'cantidad' => 1];

View File

@ -821,6 +821,7 @@ try {
$stmtCurrent = $pdo->prepare('SELECT estado, numero_cuenta_enviado_at, numero_cuenta_sede_id, numero_cuenta_dni, user_id, assigned_at, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, eliminado_at, eliminado_por, ruta_contraentrega_pedido_id, pedido_rotulado_pedido_id FROM callcenter_test_tracking WHERE source_key = ? LIMIT 1');
$stmtCurrent->execute([$sourceKey]);
$currentTracking = $stmtCurrent->fetch(PDO::FETCH_ASSOC) ?: null;
$tieneRutaContraentrega = isset($currentTracking['ruta_contraentrega_pedido_id']) && (int) $currentTracking['ruta_contraentrega_pedido_id'] > 0;
$sourceOrderForAuth = cc_test_fetch_source_order($pdo, $sourceKey);
$sourceStoreKeyForAuth = trim((string) ($sourceOrderForAuth['store_key'] ?? ''));
@ -1023,7 +1024,7 @@ try {
}
}
if ($subirALogistica && !in_array($estado, ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'], true)) {
if ($subirALogistica && !in_array($estado, ['CONFIRMADO CONTRAENTREGA', 'CONFIRMADO ENVIO'], true) && !$tieneRutaContraentrega) {
throw new RuntimeException('Para subir el pedido, el estado debe ser CONFIRMADO CONTRAENTREGA o CONFIRMADO ENVIO.');
}
@ -1275,7 +1276,7 @@ try {
throw new RuntimeException('No encontré el pedido base para enviarlo a logística.');
}
if ($estado === 'CONFIRMADO CONTRAENTREGA') {
if ($estado === 'CONFIRMADO CONTRAENTREGA' || $tieneRutaContraentrega) {
$routePayload = cc_test_prepare_route_order_payload(
$sourceOrder,
$formData,
@ -1356,7 +1357,7 @@ try {
if ($moverAPromoFinal && $promoFinalEvidencePath !== null) {
$message = 'Pedido movido a Promo Final correctamente.';
}
if ($subirALogistica && $estado === 'CONFIRMADO CONTRAENTREGA' && $rutaPedidoId !== null) {
if ($subirALogistica && ($estado === 'CONFIRMADO CONTRAENTREGA' || $tieneRutaContraentrega) && $rutaPedidoId !== null) {
$message = $rutaAction === 'updated'
? 'Pedido actualizado en Ruta Contraentrega #' . $rutaPedidoId . '.'
: 'Pedido subido a Ruta Contraentrega #' . $rutaPedidoId . '.';