DocIndex::contextoIA() selecciona los documentos relevantes a la pregunta y devuelve su texto. Reutiliza el filtrado por rol ya existente: al contexto que se envía al modelo solo entra lo que ese usuario podría leer por su cuenta en el módulo Soporte, así el asistente no puede revelar contenido restringido. Verificado: un recepcionista preguntando por permisos recibe solo el manual básico, sin el SQL ni los detalles internos que sí recibe un administrador. - services/GeminiService.php concentra la llamada a la API y la contabilidad del presupuesto, que antes vivía dentro de ai_chat.php y ahora comparten los dos asistentes. - La LIA del turnero suma la documentación a su contexto operativo, así responde tanto "cuánto facturamos hoy" como "cómo marco un paciente ausente". - modules/soporte/api/ai_docs.php es el asistente de la documentación: no accede a datos de pacientes ni de la operación, solo a los documentos visibles. A diferencia del anterior no exige acceso al turnero, así que lo puede usar cualquier usuario autenticado desde la página de documentación. - La respuesta cita de qué documentos salió. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
3.8 KiB
PHP
97 lines
3.8 KiB
PHP
<?php
|
|
/**
|
|
* modules/soporte/api/ai_docs.php
|
|
* POST { pregunta: string, historial?: [{rol,texto},...] }
|
|
*
|
|
* LIA respondiendo únicamente sobre la documentación del sistema. El contexto
|
|
* lo arma DocIndex, que filtra por rol: solo entra lo que este usuario podría
|
|
* leer por su cuenta en el módulo Soporte. No accede a datos de pacientes ni
|
|
* de la operación del día.
|
|
*/
|
|
|
|
require_once __DIR__ . '/../../../config/config.php';
|
|
require_once __DIR__ . '/../../../services/GeminiService.php';
|
|
require_once __DIR__ . '/../DocIndex.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
function salir(array $datos, int $codigo = 200): void
|
|
{
|
|
http_response_code($codigo);
|
|
echo json_encode($datos, JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
if (!isUserLoggedIn()) salir(['ok' => false, 'error' => 'No autenticado'], 401);
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') salir(['ok' => false, 'error' => 'Método no permitido'], 405);
|
|
|
|
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
$pregunta = trim($body['pregunta'] ?? '');
|
|
|
|
if ($pregunta === '') salir(['ok' => false, 'error' => 'Pregunta vacía'], 400);
|
|
if (mb_strlen($pregunta) > 800) salir(['ok' => false, 'error' => 'Pregunta demasiado larga'], 400);
|
|
|
|
$gemini = new GeminiService(Database::getInstance()->getConnection());
|
|
|
|
if (!$gemini->hayClave()) {
|
|
salir(['ok' => false, 'error' => 'El asistente no está configurado. Avisa a un administrador.'], 503);
|
|
}
|
|
if ($gemini->presupuestoAgotado()) {
|
|
salir(['ok' => false, 'error' => '⚠️ Se agotaron los tokens de LIA. Solicita tokens a soporte para continuar.'], 402);
|
|
}
|
|
|
|
// ── Documentación visible para este usuario, acotada a la pregunta ──
|
|
[$contexto, $titulos] = DocIndex::contextoIA($pregunta);
|
|
|
|
if ($contexto === '') {
|
|
salir([
|
|
'ok' => true,
|
|
'respuesta' => 'No encontré nada sobre eso en la documentación a la que tenés acceso. '
|
|
. 'Probá con otras palabras, o revisá el índice de la izquierda.',
|
|
'fuentes' => [],
|
|
]);
|
|
}
|
|
|
|
// ── Historial de la conversación ───────────────────────────────
|
|
$historial = [];
|
|
foreach (array_slice(is_array($body['historial'] ?? null) ? $body['historial'] : [], -6) as $h) {
|
|
$texto = trim((string)($h['texto'] ?? ''));
|
|
if ($texto === '') continue;
|
|
$historial[] = [
|
|
'role' => (($h['rol'] ?? '') === 'model') ? 'model' : 'user',
|
|
'parts' => [['text' => mb_substr($texto, 0, 1500)]],
|
|
];
|
|
}
|
|
|
|
$prompt = <<<PROMPT
|
|
Eres LIA, la asistente de documentación del ERP del Laboratorio Clínico Ximena Caicedo.
|
|
|
|
Respondes preguntas sobre cómo usar el sistema y cómo funciona, apoyándote ÚNICAMENTE en la
|
|
documentación incluida abajo. Reglas:
|
|
|
|
- Si la respuesta no está en la documentación, dilo con claridad. No inventes ni supongas.
|
|
- La documentación incluida es la que este usuario tiene permitido consultar. No menciones ni
|
|
deduzcas la existencia de contenido que no esté aquí.
|
|
- Responde en español, directo y práctico. Si es un procedimiento, enumera los pasos.
|
|
- Usa tablas o viñetas cuando ayuden a leer.
|
|
- No inventes rutas, nombres de botones ni consultas SQL que no aparezcan en la documentación.
|
|
|
|
DOCUMENTACIÓN DISPONIBLE:
|
|
{$contexto}
|
|
PROMPT;
|
|
|
|
try {
|
|
$r = $gemini->preguntar($prompt, $pregunta, $historial);
|
|
salir([
|
|
'ok' => true,
|
|
'respuesta' => $r['respuesta'],
|
|
'truncada' => $r['truncada'],
|
|
'fuentes' => $titulos,
|
|
'tokens_usados' => $r['tokens_usados'],
|
|
'tokens_restantes' => $r['tokens_restantes'],
|
|
'tokens_max' => $r['tokens_max'],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
salir(['ok' => false, 'error' => $e->getMessage()], 502);
|
|
}
|