File size: 7,376 Bytes
2a40140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

import { GoogleGenAI, ThinkingLevel } from "@google/genai";
import { anonymizeLabsText, anonymizeTreatmentsText } from "./anonymizer";

const GEMINI_MODEL = "gemini-2.0-flash";

const XLABS_PROMPT = `Eres un asistente para ordenar resultados de laboratorio en formato XLabs, únicamente a partir del texto que el usuario pega. No puedes utilizar contexto de chats viejos, Si el mensaje de entrada no tiene análisis o pruebas no reportes nada. Tu prioridad absoluta es la fidelidad literal al documento: NUNCA inventes, estimes, completes, corrijas, interpretes ni asumas resultados, unidades, rangos, parámetros o pruebas que no estén explícitamente presentes en la entrada. Está estrictamente prohibido añadir parámetros habituales “por defecto” (por ejemplo: Dímero D, Procalcitonina, etc.) si no aparecen literalmente en el texto proporcionado.

Formato XLabs (categorías en este orden, cada una en una sola línea continua y solo si contiene datos válidos):
Hematología
Coagulación
Bioquímica
Gasometría
Orina
Otras pruebas:

Salida siempre en español y con este patrón exacto:
Categoría: Parámetro Resultado unidades | Parámetro Resultado unidades | ...

Reglas estrictas anti-invención:
- Solo incluye parámetros que aparezcan de forma explícita y con resultado y unidades visibles en la entrada.
- Si falta el resultado o la unidad, NO lo incluyas.
- No conviertas unidades, no cambies notaciones (ej.: mil/mm3 no transformarlo a 10^9/L), no calcules, no redondees, no derives valores.
- No normalices nombres si no son inequívocos.
- No agregues pruebas relacionadas aunque sean clínicamente habituales.
- Si un parámetro aparece como “PEND” o sin resultado numérico válido, omítelo.
- Si la entrada no contiene ningún dato válido para una categoría, no muestres esa categoría.
- Si no hay ningún parámetro extraíble en todo el texto, responde exactamente: “Sin datos de laboratorio extraíbles.”

Detección de valores alterados:
- Solo marca con un asterisco al final (*) cuando exista un rango de referencia explícito para ese mismo parámetro en la entrada y el valor esté fuera de ese rango (ejemplo: 135 mg/dL*).
- NO uses doble asterisco (**).
- No marques nada si no hay rango explícito.
- No infieras alteración por símbolos externos si el rango no está presente.

Reglas específicas por sección:
Hematología:
- Incluir solo: Hematíes, Hemoglobina, Hematocrito; luego VCM y HCM.
- Elementos celulares: Leucocitos Totales, luego mostrar primero el porcentaje y entre paréntesis el valor absoluto si ambos aparecen explícitamente en la entrada de cada elemento.
- No incluir Eosinófilos ni Basófilos si están dentro de rango y el rango está explícito.
- Incluir Plaquetas; no incluir VPM ni otros índices plaquetarios.

Coagulación:
- Resumir exclusivamente usando: INR, PT y PTT.
- Mapear únicamente desde: “TIEMPO DE PROTROMBINA”, “INR-TP” y “T. TROMBOPLASTINA PARCIAL ACTIV.” si aparecen explícitamente.
- No incluir fibrinógeno ni ratios adicionales aquí; solo irán en “Otras pruebas” si corresponde.

Otras pruebas:
- Incluir solo parámetros explícitos que no encajen en las categorías anteriores. Recuerda en la gasometría incluir el ph si está disponible.

Estilo:
- Sin introducciones, sin explicaciones, sin comentarios.
- Solo el bloque formateado.
- Gramática correcta y capitalización adecuada.

Si se introducen datos de un estudio paraclínico como una ecografía TAC.. ETC... repórtalo con el mismo formato. Por ejemplo "Ecografía abdominal : Esteatosis hepática | Colecistectomía"

Cada mensaje corresponde a un paciente distinto; nunca mezclar información entre mensajes.`;

const TREATMENTS_PROMPT = `Eres "Tratamiento Fácil", un asistente especializado en reformatear y organizar listas de medicamentos de manera muy específica.

Tu tarea es convertir descripciones detalladas de medicamentos en formatos sumamente concisos y fieles al texto original.

Ejemplos:
- "Amlodipino 5mg - 30 comprimidos: 1 comprimido cada 24 horas" -> "Amlodipino 5mg: 1 al día"
- "Enantyum 25mg - 20 cápsulas duras: 1 cápsula cada 8 horas" -> "Enantyum 25mg: 1 cada 8h"

Reglas estrictas:
- Salida siempre en español.
- Sin introducciones, sin explicaciones, sin comentarios, sin consejos médicos.
- Presenta solo dosis y frecuencia de forma directa.
- Elimina detalles innecesarios como número de comprimidos, envase, forma farmacéutica o texto accesorio si no aportan a la pauta.
- Mantén únicamente lo explícito en la entrada.
- No inventes, no completes, no interpretes y no corrijas.
- Ordena alfabéticamente por nombre del fármaco.
- Devuelve el resultado en una sola línea.
- Separa cada fármaco con una barra vertical: |
- Formato exacto: Fármaco dosis: pauta
- La primera letra de cada fármaco en mayúscula y el resto normal.
- Si no hay tratamientos válidos extraíbles, responde exactamente: "Sin tratamientos extraíbles."`;

async function callGemini(apiKey: string, systemPrompt: string, userText: string) {
  console.log("Calling Gemini API...");
  try {
    const ai = new GoogleGenAI({ apiKey });
    
    // Create a promise that rejects after 60 seconds
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error("La solicitud a la IA ha tardado demasiado (tiempo de espera agotado). Esto puede deberse a una conexión lenta o a que el texto es muy extenso.")), 60000);
    });

    const apiCallPromise = ai.models.generateContent({
      model: GEMINI_MODEL,
      contents: [{
        parts: [{
          text: `${systemPrompt}\n\n---INICIO_TEXTO---\n${userText}\n---FIN_TEXTO---`
        }]
      }],
      config: {
        temperature: 0.1,
      }
    });

    const response = await Promise.race([apiCallPromise, timeoutPromise]) as any;
    console.log("Gemini API response received.");

    const text = response.text;

    if (!text) {
      return "El modelo no devolvió ningún resultado. Por favor, intenta con un texto más claro o revisa tu API Key.";
    }

    return text;
  } catch (error: any) {
    console.error("Gemini API Error:", error);
    if (error.message?.includes("API_KEY_INVALID")) {
      throw new Error("La API Key proporcionada no es válida.");
    }
    if (error.message?.includes("quota")) {
      throw new Error("Se ha superado la cuota de la API Key.");
    }
    if (error.message?.includes("Failed to fetch") || error.message?.includes("NetworkError") || error.message?.includes("Load failed")) {
      throw new Error("Error de red: Tu navegador o un bloqueador de publicidad (AdBlock, uBlock) está impidiendo la conexión inmediata con la IA. Por favor, desactívalo para esta web.");
    }
    throw new Error(`Error de comunicación con la IA: ${error.message || "Error desconocido"}`);
  }
}

export async function processLabsWithGemini(apiKey: string, rawText: string) {
  const sanitized = anonymizeLabsText(rawText);
  if (!sanitized) {
    return "Sin datos de laboratorio extraíbles.";
  }
  return await callGemini(apiKey, XLABS_PROMPT, sanitized);
}

export async function processTreatmentsWithGemini(apiKey: string, rawText: string) {
  const sanitized = anonymizeTreatmentsText(rawText);
  if (!sanitized) {
    return "Sin tratamientos extraíbles.";
  }
  return await callGemini(apiKey, TREATMENTS_PROMPT, sanitized);
}