beginTransaction(); try { $sesionId = obtenerOCrearSesionHoy(); // Lock de sesión para serializar la creación de turnos $pdo->prepare('SELECT id FROM turnero_sesiones WHERE id = ? FOR UPDATE') ->execute([$sesionId]); // Buscar prioridad_id $stmt = $pdo->prepare( 'SELECT id FROM turnero_prioridades WHERE codigo = ? AND activo = 1 LIMIT 1' ); $stmt->execute([$prioCodigo]); $prioridad = $stmt->fetch(PDO::FETCH_ASSOC); if (!$prioridad) { $pdo->rollBack(); jsonError("La prioridad '$prioCodigo' no existe o no está activa.", 422); } $prioridadId = (int) $prioridad['id']; // Buscar paciente por número de documento en lab_pacientes $pacienteNombre = $pacienteDoc; $pacienteId = null; $pacienteTel = $pacienteCel; // fallback: celular del kiosko if ($pacienteDoc) { try { $stmtP = $pdo->prepare( 'SELECT id, nombre_completo, telefono FROM lab_pacientes WHERE numero_documento = ? AND is_active = 1 LIMIT 1' ); $stmtP->execute([$pacienteDoc]); $pacRow = $stmtP->fetch(PDO::FETCH_ASSOC); if ($pacRow) { $pacienteNombre = $pacRow['nombre_completo']; $pacienteId = (int) $pacRow['id']; $pacienteTel = $pacRow['telefono'] ?: $pacienteCel; } } catch (\Throwable $e) { $pacienteNombre = $pacienteDoc; } } // Correlativo independiente por prioridad (A-001, A-002... B-001, B-002... etc.) $numero = siguienteNumeroPorPrioridad($sesionId, $prioridadId); // Código visual: "A001", "B001", "E001", etc. $codigo = $prioCodigo . str_pad($numero, 3, '0', STR_PAD_LEFT); $stmt = $pdo->prepare( 'INSERT INTO turnero_turnos (sesion_id, numero, codigo, prioridad_id, paciente_nombre, paciente_cel, paciente_id, estado, creado_at) VALUES (?, ?, ?, ?, ?, ?, ?, "espera", NOW())' ); $stmt->execute([ $sesionId, $numero, $codigo, $prioridadId, $pacienteNombre ?: null, $pacienteCel ?: null, $pacienteId, ]); $turnoId = (int) $pdo->lastInsertId(); $pdo->commit(); notificarSSE($sesionId); // ── Crear registro de consentimiento (siempre, con o sin teléfono) ── $enlaceConsentimiento = null; $esSoloMuestra = ($prioCodigo === 'F'); if (!$esSoloMuestra) { try { $stmtForms = $pdo->prepare( "SELECT tlc.formulario_id, l.formulario_modo FROM turnero_lugar_consentimientos tlc JOIN lab_formularios f ON f.id = tlc.formulario_id AND f.is_active = 1 JOIN turnero_lugares l ON l.id = tlc.lugar_id AND l.tipo = 'recepcion' AND l.activo = 1 ORDER BY tlc.formulario_id ASC LIMIT 1" ); $stmtForms->execute(); $formRow = $stmtForms->fetch(\PDO::FETCH_ASSOC); if ($formRow) { $conToken = sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0x0fff)|0x4000, mt_rand(0,0x3fff)|0x8000, mt_rand(0,0xffff), mt_rand(0,0xffff), mt_rand(0,0xffff) ); $pdo->prepare( "INSERT IGNORE INTO turnero_consentimientos (turno_id, formulario_id, token, estado) VALUES (?, ?, ?, 'pendiente')" )->execute([$turnoId, (int)$formRow['formulario_id'], $conToken]); // Solo generar enlace si el modo es 'link' (embebido no usa WA) if (($formRow['formulario_modo'] ?? 'link') === 'link') { $baseUrl = defined('BASE_URL') ? rtrim(BASE_URL, '/') : ''; $enlaceConsentimiento = $baseUrl . '/ver_formulario_enviado.php?token=' . urlencode($conToken); } } } catch (\Throwable $e) { // Sin consentimiento disponible, continuar sin link } } // ── Enviar notificación WhatsApp ── if ($pacienteTel) { try { $stmtCfg = $pdo->prepare( "SELECT clave, valor FROM lab_config WHERE clave IN ('turnero_wa_template','turnero_wa_template_muestra','turnero_wa_lang','turnero_wa_kiosko_enabled')" ); $stmtCfg->execute(); $cfgWA = $stmtCfg->fetchAll(PDO::FETCH_KEY_PAIR); $waKioskoEnabled = ($cfgWA['turnero_wa_kiosko_enabled'] ?? '0') === '1'; $waTemplate = $esSoloMuestra ? ($cfgWA['turnero_wa_template_muestra'] ?? 'turno_muestra_pendiente') : ($cfgWA['turnero_wa_template'] ?? 'consentimiento_turno'); // Usar idioma real de la plantilla aprobada (puede diferir del config) $stmtLang = $pdo->prepare("SELECT language_code FROM message_templates WHERE template_name = ? LIMIT 1"); $stmtLang->execute([$waTemplate]); $waLang = $stmtLang->fetchColumn() ?: ($cfgWA['turnero_wa_lang'] ?? 'es'); } catch (\Throwable $e) { $waKioskoEnabled = true; $waTemplate = 'consentimiento_turno'; $waLang = 'es'; } } if ($pacienteTel && $waKioskoEnabled) { // WhatsAppService::formatPhoneNumber maneja el formato colombiano correctamente $cel = preg_replace('/[^0-9]/', '', $pacienteTel); // Marcar como enviado si vamos a mandar WA if ($enlaceConsentimiento) { try { $pdo->prepare( "UPDATE turnero_consentimientos SET estado='enviado', enviado_at=NOW() WHERE turno_id=? AND estado='pendiente'" )->execute([$turnoId]); } catch (\Throwable $_) {} } require_once __DIR__ . '/../../../services/WhatsAppService.php'; $nombreWA = $pacienteNombre ?: 'Paciente'; $waMeta = ['canal' => 'turnero']; try { $wa = new WhatsAppService('turnero'); $rawComps = [ ['type' => 'body', 'parameters' => [['type' => 'text', 'text' => $codigo]]] ]; if ($conToken) { $rawComps[] = [ 'type' => 'button', 'sub_type' => 'url', 'index' => '0', 'parameters' => [['type' => 'text', 'text' => $conToken]], ]; } $wa->sendTemplateMessage($cel, $waTemplate, $waLang, [], [], $rawComps, $waMeta); } catch (\Throwable $eTmpl) { try { $msg = "Hola {$nombreWA}, su turno *{$codigo}* ha sido registrado."; if ($enlaceConsentimiento) { $msg .= "\n\nPor favor firme su consentimiento informado antes de ser atendido:\n{$enlaceConsentimiento}"; } $wa->sendTextMessage($cel, $msg, $waMeta); } catch (\Throwable $eTxt) { // Silenciar } } } // Contar posición en la cola $stmt = $pdo->prepare( ' SELECT COUNT(*) AS posicion FROM turnero_turnos t JOIN turnero_prioridades p ON p.id = t.prioridad_id WHERE t.estado = "espera" AND t.sesion_id = ? AND ( p.orden_peso < (SELECT orden_peso FROM turnero_prioridades WHERE id = ?) OR ( p.orden_peso = (SELECT orden_peso FROM turnero_prioridades WHERE id = ?) AND t.creado_at < (SELECT creado_at FROM turnero_turnos WHERE id = ?) ) ) ' ); $stmt->execute([$sesionId, $prioridadId, $prioridadId, $turnoId]); $posicion = (int) $stmt->fetchColumn() + 1; jsonOk([ 'turno' => [ 'id' => $turnoId, 'codigo' => $codigo, 'numero' => $numero, 'prioridad_codigo' => $prioCodigo, 'posicion_cola' => $posicion, 'sesion_id' => $sesionId, 'paciente_nombre' => $pacienteNombre ?: $pacienteDoc, ], ], "Turno $codigo asignado correctamente"); } catch (\Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } jsonError('Error al crear el turno: ' . $e->getMessage(), 500); }