Módulo Capacitación(Cursos) Matricula Estudiante (1) - [enrollStudent] 🧾 Descripción Este servicio matricula automáticamente a un estudiante en todos los programas de capacitación correspondientes a su puesto actual (designation) . El flujo toma como origen: Student (por DNI) Program Course Program Enrollment El servicio determina qué cursos pertenecen al puesto del estudiante , identifica qué programas contienen esos cursos y finalmente crea matrículas individuales para cada programa en el ERP. 🚀 Endpoint POST /enroll-student 📥 Parámetros (Request Body) { "dni" : "string" } Campo Tipo Obligatorio Descripción dni string Sí DNI del estudiante para buscarlo en el ERP 🔐 Seguridad Requiere autenticación interna mediante: dbErp() para consultas SQL ServiceErp() para consumo REST de recursos del ERP 🧠 Flujo del Servicio (resumen) 1. Validar parámetro dni Si no se envía DNI: → retorna error inmediato. 2. Buscar al estudiante en tabStudent SELECT name, puesto, joining_date FROM tabStudent WHERE dni = < dni > ORDER BY joining_date DESC LIMIT 1 Si no existe: → retorna "no se encontró al estudiante" . 3. Obtener todos los Programas del ERP GET /resource/Program?fields=["name"]&limit=None Por cada programa: Se consulta /resource/Program/ Se leen los cursos incluidos en ese programa Se construye el mapa: curso → [programas donde aparece] 4. Obtener todos los Cursos del ERP GET /resource/Course?fields=["name"] Por cada curso: Se consulta /resource/Course/ Se revisan los designation asociados al curso Se construye el mapa: puesto → [cursos habilitados para ese puesto] 5. Validar si el puesto del estudiante tiene cursos asignados Si no tiene cursos asociados: → retorna "No se encontró programas para este puesto" . 6. Determinar los programas finales Para cada curso del puesto, buscar en qué programas aparece y construir la lista final: programas_finales = unique(programas_permitidos) Si la lista queda vacía: → no existen programas asignados a este puesto. 7. Registrar las matrículas (Program Enrollment) Para cada programa identificado: Validar si ya existe matrícula: GET Program Enrollment?filters= [ [ "student" , "=" , ] , [ "program" , "=" , ] ] Si existe , se almacena como encontrado. Si no existe , se crea: POST Program Enrollment { "student" : "" , "program" : "" , "enrollment_date" : "" , "academic_year" : "" , "docstatus" : 1 } El servicio separa: Matrículas creadas Matrículas ya existentes Errores durante el registro 📤 Respuesta 200 – Ejemplo { "valor" : true , "msn" : "Lista de programas" , "data" : [ "PROG-001" , "PROG-005" ] , "enrollment" : [ { "name" : "ENR-0001" } , { "name" : "ENR-0002" } ] , "errors" : [ ] } ❗ Posibles Errores 1. Falta DNI { "valor" : false , "msn" : "Falta enviar \"student\"" } 2. Estudiante no encontrado { "valor" : false , "msn" : "no se encontró al estudiante" } 3. Puesto sin cursos asociados { "valor" : false , "msn" : "No se Encontró programas para este puesto" } 4. Error al registrar un enrollment Se devuelve en errors : { "errors" : [ "mensaje del error interno" ] } 🗃 Esquemas usados en el ERP Student { "name" : "string" , "dni" : "string" , "puesto" : "string" } Program { "name" : "string" , "courses" : [ { "course" : "string" } ] } Course { "name" : "string" , "designation" : [ { "designation" : "string" } ] } Program Enrollment { "student" : "string" , "program" : "string" , "enrollment_date" : "date" , "academic_year" : "string" , "docstatus" : 1 } 🧩 Lógica en Pseudocódigo dni = request.dni if empty(dni): return error student = query Student where dni=dni order by joining_date desc limit 1 if not found: return error designation = student.puesto programas = GET all programs mapProgramas = generar mapa curso → programas cursos = GET all courses mapCursos = generar mapa puesto → cursos if designation not in mapCursos: return error programas_final = programas donde cursos del puesto están incluidos foreach programa in programas_final: if enrollment existe: agregar a enrollments encontrados else: crear enrollment agregar respuesta a enrollments Listar Cursos (1) - [listar] 🧾 Descripción Este servicio obtiene todos los cursos asignados a un empleado o estudiante, basándose en su puesto (designation / puesto académico). El flujo evalúa primero si el DNI pertenece a un Empleado , y si no, valida si pertenece a un Estudiante . Con el puesto encontrado, se consultan los cursos vinculados y finalmente se retornan los detalles del curso desde tabCourse . El servicio interactúa con: tabEmployee tabStudent tabPuesto Curso tabCourse 🚀 Endpoint POST /listar 📥 Request Body { "dni" : "string" } Campo Tipo Obligatorio Descripción dni string ✔ DNI del empleado o estudiante para identificar su puesto. 🔐 Seguridad Requiere autenticación interna del ERP vía dbErp() . No requiere token externo del cliente. 🧠 Flujo del Servicio (Resumen real) 1. Buscar si el DNI pertenece a un Empleado activo Consulta a:  tabEmployee con filtros: passport_number = DNI status = Active designation IS NOT NULL Si se encuentra → extrae el designation . 2. Si no es empleado, verificar si es Estudiante activo Consulta a:  tabStudent con filtros: dni = DNI enabled = 1 Si se encuentra → extrae el campo puesto . 3. Si no existe puesto asignado Retorna mensaje: "valor" : false "msn" : "No se encontro el puesto de empleado" 4. Buscar cursos asignados al puesto Consulta a:  tabPuesto Curso filtrando por: designation = puestoEmpleado Obtiene los parent → IDs de los cursos asignados. 5. Obtener información de los cursos Consulta a:  tabCourse Retorna campos: name hero_image 6. Respuesta final Se devuelve el listado de cursos con sus imágenes. 📤 Response 200 – Ejemplo { "valor" : true , "data" : [ { "name" : "CURS-001" , "hero_image" : "/files/curso_001.png" } , { "name" : "CURS-002" , "hero_image" : "/files/curso_002.png" } ] } ❗ Posibles Errores 1. No existe empleado NI estudiante con ese DNI { "valor" : false , "msn" : "No se encontro el puesto de empleado" } 2. Puesto sin cursos configurados { "valor" : false , "msn" : "Su usuario no tiene cursos asignados" } 3. Error al obtener datos del ERP { "valor" : false , "msn" : "Error al consultar datos" , "data" : [ ] } 📚 Tablas y Campos utilizados 🔸 tabEmployee Campos: passport_number designation status 🔸 tabStudent Campos: dni enabled puesto 🔸 tabPuesto Curso Campos: designation parent (ID del curso) 🔸 tabCourse Campos: name hero_image 🗃 Lógica en pseudo-código dni = request.dni employee = GET tabEmployee WHERE passport_number = dni AND status = 'Active' if employee exists: puesto = employee.designation else: student = GET tabStudent WHERE dni = dni AND enabled = 1 if student exists: puesto = student.puesto else: return error("No se encontro el puesto de empleado") cursosAsignados = GET tabPuesto Curso WHERE designation = puesto if cursosAsignados empty: return error("Su usuario no tiene cursos asignados") idsCursos = cursosAsignados.parents infoCursos = GET tabCourse WHERE name IN idsCursos return infoCursos Cursos Temas (1, 2, 3, 4, 5, 6, 7) - CAPACITACIÓN ATC (II FASE) - [temas] 🧾 Descripción Obtiene los temas (topics) de un curso específico y calcula cuántos videos y artículos contiene cada tema. Este servicio consulta: La información completa del Course El detalle de cada Topic Cuenta los contenidos según su tipo: Video o Article Está diseñado para mostrar al usuario un resumen claro del material disponible por tema. 🚀 Endpoint GET /temas 📥 Parámetros (Request) Se recibe un único valor desde el request: { "curso" : "ID_DEL_CURSO" } 🔐 Seguridad Este servicio requiere autenticación válida hacia el ERP mediante: ServiceErp ("GET", ...) El token o cookies activos son manejados internamente por la clase general . 🧠 Flujo del Servicio (resumen real) Recibe el curso por parámetro. Consulta al ERP el recurso: GET resource /Course/ {curso} Verifica que el curso exista; si no, devuelve error: Su usuario no tiene cursos asignados Extrae la lista de topics del curso. Para cada topic: Inicializa sus contadores: videos = 0 articulos = 0 Consulta el contenido del tópico: GET resource /Topic/ {topic_id} Recorre el contenido ( topic_content ): Si content_type == Video → incrementa contador de videos Si content_type == Article → incrementa contador de artículos Retorna la lista de temas con sus conteos. 📤 Response 200 – Ejemplo { "valor" : true , "data" : [ { "topic" : "TP-001" , "title" : "Introducción" , "videos" : 3 , "articulos" : 1 } , { "topic" : "TP-002" , "title" : "Conceptos Básicos" , "videos" : 1 , "articulos" : 2 } ] } ❗ Posibles Errores 1. Curso no existe o no tiene permisos { "valor" : false , "msn" : "Su usuario no tiene cursos asignados" } 2. El tópico no contiene información (se ignora sin romper el flujo) No retorna error, simplemente no se contabiliza. 3. Fallo de conexión con ERP { "valor" : false , "msn" : "Error al consultar datos del ERP" } 📚 Schemas Consultados Course Campos usados: { "topics" : [ { "topic" : "string" , "title" : "string" } ] } Topic Campos usados: { "topic_content" : [ { "content_type" : "Video | Article" , "content" : "string" } ] } 🗃 Lógica en pseudo-código curso = request.curso courseData = GET Course/{curso} if !courseData.exists: return error("Su usuario no tiene cursos asignados") topics = courseData.topics for each topic in topics: topic.videos = 0 topic.articulos = 0 topicContent = GET Topic/{topic.id} for content in topicContent: if content.type == "Video": topic.videos++ if content.type == "Article": topic.articulos++ return topics Cursos Temas Contenido (1, 2, 3, 4, 5, 6, 7) - [temas_contenido] 🧾 Descripción Obtiene todo el contenido asociado a un Tema de Capacitación , enriqueciendo la información con: URL del video (si el contenido es Video) Texto o enlace procesado del artículo (si el contenido es Article) Cantidad de intentos realizados en un quiz por un estudiante (si corresponde) El servicio puede opcionalmente recibir el DNI del estudiante , para verificar su progreso (intentos de quiz). Este servicio integra información desde varios recursos del ERP: Topic Video Article Student Quiz Activity 🚀 Endpoint GET /temas-contenido 📌 Parámetros vía request: Parámetro Tipo Obligatorio Descripción tema string ✔️ Nombre del Topic a consultar dni string ❌ DNI del estudiante para obtener intentos de quiz 🔐 Seguridad Requiere autenticación interna vía: $this ->general-> ServiceErp (...) No necesita token del cliente, se usa credencial del backend. 🧠 Flujo del Servicio (Resumen real) 1️⃣ Si viene DNI → Obtener datos del estudiante Consulta: GET Student ? limit = None &fields=["name","puesto"] &filters=[["enabled","=", 1 ],["dni","like","%{dni}%"]] Guarda name del estudiante si existe. 2️⃣ Obtener el tema solicitado GET Topic/{tema} Debe existir; si no, devuelve:  { "valor" : false , "msn" : "No se encontró el tema del curso" } 3️⃣ Recorrer cada elemento de “topic_content” Para cada ítem: 📽 Si es Video: GET Video /{content_id} Agrega:  "url" : "" 📄 Si es Article: GET Article /{content_id} Se extrae contenido sin HTML ( strip_tags ) Si dentro del texto hay un link, se devuelve solo ese link Se agrega: "url" : "" ❓ Si es Quiz y hay estudiante: GET Quiz Activity ?filters= [["quiz","=",content_id],["student","=", estudiante]] Agrega:  "quiz_attempts" : Si no hay estudiante o no es quiz:  "quiz_attempts" : 0 4️⃣ Retornar todos los contenidos enriquecidos 📥 Request Body (ejemplo) { "tema" : "TOPIC-001" , "dni" : "12345678" } 📤 Response 200 – Ejemplo { "valor" : true , "data" : [ { "content_type" : "Video" , "content" : "VID-01" , "url" : "https://erp/video1.mp4" , "quiz_attempts" : 0 } , { "content_type" : "Article" , "content" : "ART-01" , "url" : "https://articulo.com/content" , "quiz_attempts" : 0 } , { "content_type" : "Quiz" , "content" : "QUIZ-01" , "quiz_attempts" : 2 } ] } ❗ Posibles Errores 1. Tema no encontrado { "valor" : false , "msn" : "No se encontró el tema del curso" } 2. Estudiante no existe (cuando se envía DNI) No genera error, simplemente no agrega intentos de quiz. 3. Contenido sin URL o datos incompletos El servicio ignora valores faltantes y continúa procesando. 4. Error del servidor { "valor" : false , "msn" : "Error del servidor" , "data" : "" } 📚 Recursos / Esquemas usados Topic { "topic_content" : [ { "content_type" : "Video | Article | Quiz" , "content" : "string" } ] } Video { "url" : "string" } Article { "content" : "html|string" } Student { "name" : "string" , "dni" : "string" , "puesto" : "string" } Quiz Activity { "quiz" : "string" , "student" : "string" } 🗃 Pseudocódigo del servicio if DNI: estudiante = GET Student by DNI topic = GET Topic/{tema} foreach content in topic.topic_content: if content_type == Video: obtener url del video if content_type == Article: obtener contenido, limpiar html, buscar link if content_type == Quiz and estudiante existe: obtener intentos realizados return lista enriquecida de contenidos Examen del Curso (1, 2, 3) - EXAMEN ATC - Agregar contacto - [preguntas] 🧾 Descripción Obtiene todas las preguntas de un examen (Quiz) registrado en el ERP, junto con sus opciones, respuestas correctas y metadatos. El servicio se encarga de: Consultar el examen ( Quiz ) en la base ERP. Traer todas las preguntas asociadas. Traer todas las opciones de cada pregunta. Reorganizar los resultados del SQL (que vienen aplanados) en una estructura jerárquica: Pregunta Opciones Convertir el contenido HTML de la pregunta a texto plano. Detectar si la pregunta incluye una imagen embebida. Preparar la respuesta final en un formato limpio y listo para usar en la app. 🚀 Endpoint POST /preguntas 📥 Request Body { "examen" : "QUIZ-0001" } Parámetros Campo Tipo Obligatorio Descripción examen string ✔️ ID del cuestionario (Quiz.name) 🔐 Seguridad Este servicio requiere acceso a la API interna del ERP a través de: dbErp() → Consulta SQL interna No usa token del usuario final. La autenticación es interna del servidor. 🧠 Flujo del Servicio (Explicación Real) 1. Construye consulta SQL El servicio arma un query que une: tabQuiz tabQuiz Question tabQuestion tabOptions De esta manera obtiene: Datos de la pregunta Datos de cada opción Datos del examen 2. Envía la consulta Se ejecuta:  POST method /send-query- database 3. Reorganiza la data El ERP devuelve una fila por cada opción. El servicio agrupa todo por:  question.name Generando esta estructura: [ { question: "..." , question_link: "..." , options: [ { option: "..." , is_correct: 0 /1 } ] } ] 4. Convierte HTML → texto Las preguntas vienen como HTML. El servicio: Analiza el HTML con html_to_obj() Extrae texto plano ( strip_tags ) Busca imágenes incrustadas y genera la URL completa. 5. Construye la respuesta final Para cada pregunta: name question (texto plano) imagen (si existe) options (lista de alternativas) 📤 Response 200 – Ejemplo { "valor" : true , "data" : [ { "name" : "PREG-001" , "owner" : "admin@example" , "creation" : "2025-01-01" , "modified" : "2025-01-01" , "modified_by" : "admin@example" , "idx" : 1 , "docstatus" : 0 , "question" : "¿Cuál es la capital de Francia?" , "question_type" : "Opciones" , "question_link" : "PREG-001" , "options" : [ { "name" : "OPT-001" , "option" : "Madrid" , "is_correct" : 0 , "doctype" : "Options" } , { "name" : "OPT-002" , "option" : "París" , "is_correct" : 1 , "doctype" : "Options" } ] , "imagen" : "" } ] } ❗ Posibles Errores 1. Examen no encontrado { "valor" : false , "msn" : "No existe información del examen" , "data" : [ ] } 2. Error en consulta SQL { "valor" : false , "msn" : "Error al obtener preguntas" , "data" : "" } 3. Examen sin preguntas { "valor" : true , "data" : [ ] } 📚 Schemas Usados 🔸 Quiz Campo Tipo name string 🔸 Question Campo Tipo name string question HTML question_type string 🔸 Options Campo Tipo name string option string is_correct int (0 o 1) parent question.name 🗃 Lógica en pseudo-código examen = request.examen rows = SQL: SELECT preguntas + opciones FROM Quiz JOIN Quiz Question JOIN Question JOIN Options WHERE qz.name = examen grouped = reorganizarPorPregunta (rows) foreach pregunta in grouped: procesar HTML extraer texto e imagen return JSON (valor= true , data=preguntasProcesadas) Evaluar Examen del Curso (1, 2, 3) - EXAMEN ATC - Agregar contacto - [guardar_examen_old] 🧾 Descripción Este servicio registra las respuestas de un examen enviado por un estudiante. La función: Recibe el DNI del estudiante, el identificador del examen y las respuestas. Valida que el estudiante exista en el ERP. Obtiene la definición completa del examen (Quiz) desde el ERP. Obtiene la información detallada de cada pregunta usando los question_link . Devuelve el listado de preguntas del examen, incluyendo su estructura completa. ⚠ Importante: Este servicio no guarda las respuestas del examen. Solo valida al estudiante y retorna las preguntas del examen consultado. 🚀 Endpoint POST /guardar-examen-old 📥 Request Body (JSON) { "dni" : "string" , "examen" : "string" , "respuestas" : "json_string" } Ejemplo: { "dni" : "12345678" , "examen" : "QUIZ-0001" , "respuestas" : "{\"P1\":\"A\",\"P2\":\"C\"}" } 🔐 Seguridad Requiere autenticación ERP (a través de ServiceErp() ). No utiliza tokens externos. 🧠 Flujo del Servicio (Explicación detallada) Recibe datos del request: $dni $examen $respuestas (JSON string → array) Decodifica las respuestas del examen: $respuestas = json_decode ( $respuestas , true ); Busca al estudiante por DNI (solo estudiantes habilitados): GET Student filters=[["Student","enabled","=",1],["Student","dni","like","%%"]] Si no encuentra coincidencias → ❌ devuelve error: { "valor" : false , "msn" : "Su usuario no tiene cursos asignados" } Obtiene la definición del examen (Quiz) desde el ERP: GET Quiz/ Trae todas las preguntas vinculadas. Por cada pregunta del examen: Consulta la API: GET Question/{question_link} Agrega la información completa al arreglo final. Devuelve todas las preguntas consultadas. 📤 Response 200 – Ejemplo [ { "name" : "QUESTION-001" , "question" : "¿Cuál es la capital de Perú?" , "options" : [ { "option" : "Lima" , "is_correct" : 1 } , { "option" : "Cusco" , "is_correct" : 0 } ] } , { "name" : "QUESTION-002" , "question" : "Seleccione la opción correcta..." , "options" : [ ... ] } ] ❗ Posibles Errores 1. Estudiante no existe o no está habilitado { "valor" : false , "msn" : "Su usuario no tiene cursos asignados" } 2. Error consultando Quiz o Preguntas (NOT IMPLEMENTED, pero puede ocurrir internamente) { "valor" : false , "msn" : "Error al obtener examen" , "data" : { } } 3. Respuestas mal formateadas Si el JSON no es válido: { "valor" : false , "msn" : "Formato inválido de respuestas" } (Nota: El código actual no valida esto, pero debería.) 📚 Schemas usados Student (GET) { "name" : "string" , "dni" : "string" , "enabled" : 1 } Quiz (GET) { "name" : "string" , "question" : [ { "question_link" : "QUESTION-001" } ] } Question (GET) { "name" : "string" , "question" : "string" , "options" : [ { "option" : "string" , "is_correct" : 0 | 1 } ] } 🗃 Lógica en pseudocódigo dni = request.dni examen = request.examen respuestas = json_decode(request.respuestas) student = GET Student WHERE enabled=1 AND dni LIKE %dni% if student not found: return error quiz = GET Quiz/{examen} preguntas = [] foreach link in quiz.question: p = GET Question/{question_link} preguntas.append(p) return preguntas