Files
whatsapp/modules/soporte/api/ai_docs.php
T
Lizandro GuarnizoandClaude Opus 5 81c007c516 LIA responde sobre la documentación, filtrada por el rol de quien pregunta
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>
2026-08-04 10:41:40 -05:00

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);
}