Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mafab9125/PDG_SISTEMA_RECOMENDADOR/llms.txt

Use this file to discover all available pages before exploring further.

El módulo src/services/gemini.ts centraliza toda la comunicación con la API de Google Gemini. Exporta tres funciones asíncronas —extractProfile, evaluateVAP y getAIRecommendation— y una función auxiliar interna, parseJSONResponse, que garantiza la extracción robusta de JSON de las respuestas del modelo.

Configuración del cliente

RAP-Rec utiliza el SDK oficial @google/genai (no @google/generative-ai). El cliente se inicializa una sola vez al cargar el módulo:
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const MODEL_NAME = "gemini-3-flash-preview";
La variable de entorno GEMINI_API_KEY debe estar disponible en el proceso que ejecuta la aplicación. Si no está definida, el módulo emite una advertencia en consola pero no lanza excepción en tiempo de inicialización; el error ocurre al primer llamado al modelo.

Funciones exportadas

extractProfile

Analiza el texto de una hoja de vida y devuelve un objeto UserProfile estructurado.
export const extractProfile = async (
  text: string,
  retries = 2
): Promise<UserProfile>
Parámetros
ParámetroTipoDescripción
textstringTexto extraído del CV (PDF o DOCX). Se trunca a los primeros 8 000 caracteres antes de enviarse al modelo.
retriesnumberNúmero de reintentos disponibles. Valor por defecto: 2.
Configuración del modelo
PropiedadValor
model"gemini-3-flash-preview"
temperature0.1 (baja, para máxima fidelidad en extracción)
responseMimeType"application/json"
Instrucción del sistema El modelo actúa como reclutador experto. La instrucción le pide extraer todos los campos relevantes del texto y devolverlos en la estructura JSON del UserProfile:
const systemInstruction = `Actúa como un reclutador experto y analista de perfiles profesionales. 
Tu tarea es extraer TODA la información relevante del texto proporcionado (CURRÍCULUM/LINKEDIN) 
e integrarla en la estructura del APPLICANTE.
...`;
Campos extraídos
  • nombre — Nombre completo del aspirante
  • email — Correo electrónico, si existe
  • phone — Teléfono, si existe
  • years_experience — Años totales de experiencia laboral (calculado)
  • current_position — Cargo actual
  • empresa — Empresa o institución actual
  • industry — Sector o industria
  • education_level — Nivel educativo más alto (Pregrado, Posgrado, etc.)
El campo formacion es un arreglo de objetos con:
  • titulo — Nombre del título o diploma
  • institucion — Universidad o entidad
  • anio — Año de graduación o "En curso"
  • nivel — Nivel (Pregrado, Posgrado, Especialización, Maestría, Doctorado)
  • technical_skills — Arreglo de habilidades técnicas
  • certifications — Arreglo de certificaciones (separadas de la educación formal)
  • relevant_projects — Arreglo de proyectos relevantes
  • area_profundizacion — Campo de interés detectado
  • contexto_aplicacion — Contexto de aplicación detectado
  • objetivo_profesional — Meta profesional detectada
  • motivation — Resumen motivacional
  • resumen — Resumen general del perfil
  • respuestas_experiencia.transformacion_pedagogica — Nivel de experiencia en transformación pedagógica
  • respuestas_experiencia.diseño_curricular — Nivel de experiencia en diseño curricular
  • respuestas_experiencia.integracion_tic — Nivel de experiencia en integración TIC
Lógica de reintentos Si la llamada falla, la función espera antes de reintentar con retardo exponencial:
if (retries > 0) {
  const delay = (3 - retries) * 1500; // 1500ms en el intento 1, 3000ms en el intento 2
  await sleep(delay);
  return extractProfile(text, retries - 1);
}

evaluateVAP

Evalúa al aspirante contra las 5 competencias del Marco VAP y devuelve un arreglo de puntuaciones con justificaciones.
export const evaluateVAP = async (
  profile: UserProfile,
  answers?: Record<string, string>,
  retries = 2
): Promise<VAPEvaluation[]>
Parámetros
ParámetroTipoDescripción
profileUserProfilePerfil completo del aspirante.
answersRecord<string, string>Respuestas de la evaluación técnica: clave de competencia → opción seleccionada. Opcional.
retriesnumberReintentos disponibles. Valor por defecto: 2.
Competencias evaluadas El conjunto completo de VAP_COMPETENCIES se serializa como JSON y se inyecta en la instrucción del sistema. Las cinco competencias son:
IDClaveNombre
C1COMUNICACION_OEComunicación Oral y Escrita en Contextos Educativos
C2INVESTIGACION_IEInvestigación-Intervención en Asuntos Educativos
C3PRAXIS_TRANSF_PEDPraxis y Transformación Pedagógica
C4DISENO_EVAL_CURRDiseño y Evaluación Curricular
C5USO_TIC_EDUUso de TIC para Enriquecer Procesos Educativos
Escala de puntuación
RangoNivel
1.0 – 2.5Novato
3.0 – 3.9Competente
4.0 – 4.5Proficiente
4.6 – 5.0Experto
Formato de respuesta El modelo devuelve un arreglo JSON donde cada elemento sigue la interfaz VAPEvaluation:
[
  {
    "competencia_id": "C1",
    "puntaje": 3.5,
    "nivel": "Competente",
    "justificacion": "El aspirante demuestra capacidad para producir textos educativos estructurados..."
  },
  {
    "competencia_id": "C2",
    "puntaje": 2.0,
    "nivel": "Novato",
    "justificacion": "Identifica problemas educativos de forma general..."
  }
]

getAIRecommendation

Genera la ruta académica personalizada cruzando los resultados de la evaluación VAP con el catálogo de cursos.
export const getAIRecommendation = async (
  profile: UserProfile,
  evaluation: VAPEvaluation[],
  retries = 2
): Promise<any>
Parámetros
ParámetroTipoDescripción
profileUserProfilePerfil del aspirante.
evaluationVAPEvaluation[]Resultados de la evaluación VAP.
retriesnumberReintentos disponibles. Valor por defecto: 2.
Lógica de cruce (cross-matching) Antes de llamar al modelo, la función serializa el catálogo de cursos con campos reducidos para no exceder el límite de tokens:
const catalogPrompt = JSON.stringify(COURSE_CATALOG.map(c => ({
  id: c.id_curso,
  nombre: c.nombre,
  competencias: c.competencias_VAP_primarias,
  desc: c.descripcion?.substring(0, 100),
  semestre: c.semestre
})));
La instrucción del sistema le indica al modelo:
  1. Identificar las competencias (C1–C5) con puntaje < 4.0.
  2. Seleccionar cursos del catálogo cuyas competencias_VAP_primarias correspondan a esas brechas.
  3. Priorizar los cursos que abordan la brecha más crítica (menor puntaje).
  4. Sugerir máximo 4 cursos.
Formato de respuesta
{
  "mensaje_estudiante": "Mensaje motivador personalizado para el aspirante...",
  "cursos_sugeridos": ["ID_CURSO_1", "ID_CURSO_2"],
  "plan_semestral": [
    {
      "semestre": 1,
      "materias": [
        {
          "nombre": "Nombre de la materia",
          "estado": "regular",
          "justificacion": "Breve explicación del estado"
        }
      ]
    }
  ],
  "analisis_director": "Justificación técnica para la flexibilización curricular..."
}
El campo estado de cada materia puede ser "regular" (materia normal del plan), "exonerada" (reconocida por experiencia previa) o "sugerida" (curso adicional para cerrar brechas). El plan_semestral debe incluir todas las materias del programa, marcando cuáles se exoneran y cuáles se agregan.

Función auxiliar: parseJSONResponse

parseJSONResponse es una función interna que extrae JSON de respuestas del modelo que pueden contener texto adicional o bloques de código Markdown.
const parseJSONResponse = (text: string) => {
  // Encuentra los límites { } o [ ] del JSON
  const jsonStart = text.indexOf('{');
  const jsonEnd = text.lastIndexOf('}');
  const arrayStart = text.indexOf('[');
  const arrayEnd = text.lastIndexOf(']');

  // Determina si la respuesta es objeto o arreglo
  let start = -1;
  let end = -1;

  if (jsonStart !== -1 && (arrayStart === -1 || jsonStart < arrayStart)) {
    start = jsonStart;
    end = jsonEnd;
  } else if (arrayStart !== -1) {
    start = arrayStart;
    end = arrayEnd;
  }

  if (start === -1 || end === -1) {
    throw new Error('No se encontró un formato JSON válido en la respuesta.');
  }

  return JSON.parse(text.substring(start, end + 1));
};
La función lanza un error descriptivo si la respuesta está vacía o no contiene un patrón JSON reconocible.

Manejo de errores y reintentos

Las tres funciones comparten una política de reintentos con retardo:
1

Primera llamada al modelo

Se ejecuta la solicitud con los parámetros configurados. Si la respuesta es exitosa, se parsea y devuelve el resultado.
2

Reintento con espera

Si ocurre un error y quedan reintentos disponibles, la función espera 1 500 ms × número_de_intento antes de reintentar automáticamente.
3

Error final

Si se agotan todos los reintentos, la función lanza un error con mensaje descriptivo. Los errores de cuota (HTTP 429) generan un mensaje específico: "Límite de cuota alcanzado. Por favor, espera un minuto.".
Si GEMINI_API_KEY no está configurada correctamente, todas las llamadas al modelo fallarán con un error de conexión. Verifica la variable de entorno antes de desplegar la aplicación.

Páginas relacionadas

Extracción de perfil

Detalles del pipeline de procesamiento de PDF y DOCX antes de llamar a extractProfile.

Marco VAP

Las 5 competencias que evaluateVAP evalúa y cómo se definen sus niveles.

Gap analysis

Cómo getAIRecommendation cruza puntajes VAP con el catálogo para generar la ruta.

Catálogo de cursos

Estructura de los cursos disponibles que se pasan a getAIRecommendation.

Build docs developers (and LLMs) love