# ET-EDU-005: Validación de Texto Abierto (Rueda de Inferencias) **Fecha:** 2025-11-21 **Versión:** 1.0 **Módulo:** 2.5 - Rueda de Inferencias **Tipo:** Especificación Técnica --- ## 1. Resumen Ejecutivo Este documento especifica cómo el sistema valida las respuestas de texto abierto (inferencias) en el ejercicio "Rueda de Inferencias". A diferencia de ejercicios con respuestas cerradas (opción múltiple, verdadero/falso), este mecanismo permite al estudiante escribir texto libre que se valida mediante **coincidencia de palabras clave (keywords)**. --- ## 2. Formato de Respuesta Esperada ### 2.1 Estructura del Frontend al Backend ```typescript // Formato que envía el frontend interface SubmitAnswerPayload { userId: string; // UUID del usuario exerciseId: string; // UUID del ejercicio answers: { fragments: Record; // { "frag-1": "texto...", "frag-2": "texto..." } categoryId?: string; // Categoría seleccionada (opcional) timeSpent?: number; // Tiempo en segundos (opcional) } } ``` ### 2.2 Ejemplo de Respuesta Válida ```json { "userId": "550e8400-e29b-41d4-a716-446655440000", "exerciseId": "ex-rueda-001", "answers": { "fragments": { "frag-1": "Marie Curie fue una pionera en el estudio de la radiactividad y ganó dos premios Nobel.", "frag-2": "Su determinación la llevó a superar los obstáculos de su época y convertirse en la primera mujer catedrática.", "frag-3": "Los cuadernos de Marie Curie siguen siendo radiactivos después de más de cien años." }, "categoryId": "cat-inferencial", "timeSpent": 142 } } ``` --- ## 3. Proceso de Validación ### 3.1 Flujo General ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ 1. RECEPCIÓN │ │ Backend recibe: { fragments: { "frag-1": "texto...", ... } } │ └──────────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 2. OBTENCIÓN DE SOLUCIÓN │ │ Base de datos retorna keywords para cada fragmento │ │ Ejemplo: ["pionera", "radiactividad", "nobel", "primera", "mujer"] │ └──────────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 3. NORMALIZACIÓN DE TEXTO │ │ - Convertir a minúsculas │ │ - Eliminar acentos (á→a, é→e, í→i, ó→o, ú→u, ñ→n) │ │ - Eliminar espacios extra │ │ Ejemplo: "Marie Curie fue PIONERA" → "marie curie fue pionera" │ └──────────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 4. VALIDACIÓN DE LONGITUD │ │ - Mínimo: 20 caracteres │ │ - Máximo: 200 caracteres │ │ Si no cumple → Respuesta inválida con feedback específico │ └──────────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 5. CONTEO DE KEYWORDS │ │ - Buscar cada keyword en el texto normalizado │ │ - Contar coincidencias (LIKE '%keyword%') │ │ - Mínimo requerido: 2 keywords │ └──────────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 6. CÁLCULO DE PUNTUACIÓN │ │ - Cada fragmento válido: 20 puntos │ │ - Score final: (puntos_obtenidos / puntos_totales) * 100 │ │ - Ejemplo: 3/5 fragmentos válidos = 60 puntos │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 Criterios de Validación por Fragmento | Criterio | Valor Default | Configurable | |----------|--------------|--------------| | Longitud mínima | 20 caracteres | ✅ Sí | | Longitud máxima | 200 caracteres | ✅ Sí | | Keywords mínimos | 2 | ✅ Sí | | Puntos por fragmento | 20 | ✅ Sí | --- ## 4. Estructura de la Solución en Base de Datos ### 4.1 Formato JSON de la Solución ```json { "validation": { "minKeywords": 2, "minLength": 20, "maxLength": 200 }, "fragments": [ { "id": "frag-1", "keywords": ["pionera", "radiactividad", "nobel", "primera", "mujer", "cientifico", "premio", "campos", "unica"], "points": 20 }, { "id": "frag-2", "keywords": ["determinacion", "obstaculos", "epoca", "primera", "mujer", "catedratica", "universidad", "superacion"], "points": 20 }, { "id": "frag-3", "keywords": ["cuadernos", "radiactivos", "legado", "cien", "anos", "investigacion", "historia", "impacto"], "points": 20 } ] } ``` ### 4.2 Descripción de Campos | Campo | Tipo | Descripción | |-------|------|-------------| | `validation.minKeywords` | integer | Número mínimo de keywords que debe contener el texto | | `validation.minLength` | integer | Longitud mínima del texto en caracteres | | `validation.maxLength` | integer | Longitud máxima del texto en caracteres | | `fragments[].id` | string | Identificador único del fragmento (debe coincidir con el enviado) | | `fragments[].keywords` | string[] | Lista de palabras clave que se buscarán en el texto | | `fragments[].points` | integer | Puntos otorgados si el fragmento es válido | --- ## 5. Ejemplos de Validación ### 5.1 Respuesta VÁLIDA **Texto del usuario:** > "Marie Curie fue una pionera en la radiactividad y recibió el premio Nobel dos veces" **Análisis:** - Longitud: 89 caracteres ✅ (entre 20-200) - Keywords encontrados: "pionera", "radiactividad", "premio", "nobel" = 4 keywords ✅ (≥ 2) - **Resultado:** VÁLIDO - 20 puntos ### 5.2 Respuesta INVÁLIDA (muy corta) **Texto del usuario:** > "Marie Curie ganó" **Análisis:** - Longitud: 16 caracteres ❌ (menor que 20) - **Resultado:** INVÁLIDO - 0 puntos - **Feedback:** "El texto es demasiado corto. Mínimo 20 caracteres, tienes 16." ### 5.3 Respuesta INVÁLIDA (sin keywords suficientes) **Texto del usuario:** > "Ella era una persona muy inteligente y trabajadora que logró muchas cosas importantes." **Análisis:** - Longitud: 87 caracteres ✅ (entre 20-200) - Keywords encontrados: 0 ❌ (ninguna keyword coincide) - **Resultado:** INVÁLIDO - 0 puntos - **Feedback:** "Tu inferencia necesita más relación con el texto. Has incluido 0 conceptos clave, pero necesitas al menos 2." ### 5.4 Respuesta PARCIALMENTE VÁLIDA (1 keyword) **Texto del usuario:** > "Marie Curie fue una mujer muy importante en la historia de la ciencia moderna." **Análisis:** - Longitud: 78 caracteres ✅ - Keywords encontrados: "mujer" = 1 keyword ❌ (necesita 2) - **Resultado:** INVÁLIDO - 0 puntos - **Feedback:** "Tu inferencia necesita más relación con el texto. Has incluido 1 conceptos clave, pero necesitas al menos 2." --- ## 6. Respuesta del Backend al Frontend ### 6.1 Estructura de Respuesta Exitosa ```json { "attemptId": "att-123-456", "score": 67, "isPerfect": false, "correctAnswersCount": 2, "totalQuestions": 3, "feedback": { "overall": "2 de 3 inferencias válidas. Revisa los fragmentos marcados para mejorar tu respuesta." }, "details": { "total_fragments": 3, "valid_fragments": 2, "total_points_possible": 60, "points_earned": 40, "percentage": 67, "validation_criteria": { "min_keywords": 2, "min_length": 20, "max_length": 200 }, "results_per_fragment": [ { "fragment_id": "frag-1", "is_valid": true, "matched_keywords": ["pionera", "radiactividad", "nobel", "premio"], "keyword_count": 4, "points": 20, "feedback": "¡Excelente! Has incluido 4 conceptos clave del fragmento." }, { "fragment_id": "frag-2", "is_valid": true, "matched_keywords": ["determinacion", "obstaculos"], "keyword_count": 2, "points": 20, "feedback": "¡Excelente! Has incluido 2 conceptos clave del fragmento." }, { "fragment_id": "frag-3", "is_valid": false, "matched_keywords": ["cuadernos"], "keyword_count": 1, "points": 0, "feedback": "Tu inferencia necesita más relación con el texto. Has incluido 1 conceptos clave, pero necesitas al menos 2." } ] }, "rewards": { "xpEarned": 67, "mlCoinsEarned": 15 } } ``` --- ## 7. Función SQL de Validación ### 7.1 Ubicación ``` apps/database/ddl/schemas/educational_content/functions/14-validate_rueda_inferencias.sql ``` ### 7.2 Funciones Disponibles | Función | Propósito | |---------|-----------| | `_validate_single_fragment()` | Función auxiliar que valida un fragmento individual | | `validate_rueda_inferencias_text()` | Valida un fragmento específico dado exercise_id y fragment_id | | `validate_rueda_inferencias()` | Wrapper estándar que valida todos los fragmentos | ### 7.3 Normalización de Texto La función `gamilit.normalize_text()` realiza: 1. Conversión a minúsculas (`LOWER()`) 2. Eliminación de acentos: á→a, é→e, í→i, ó→o, ú→u, ñ→n 3. Trim de espacios ```sql -- Ejemplo interno v_normalized_user_text := LOWER(gamilit.normalize_text(TRIM(p_user_text))); ``` --- ## 8. Configuración del Ejercicio en Seeds ### 8.1 Ejemplo de Seed Completo ```sql INSERT INTO educational_content.exercises ( id, mission_id, type, difficulty_level, title, description, instructions, config, content, solution, rubric, time_limit_seconds, xp_reward, ml_coins_reward, order_index, is_active ) VALUES ( 'ex-rueda-001'::uuid, 'mission-002'::uuid, 'rueda_inferencias'::educational_content.exercise_type, 'intermediate'::educational_content.difficulty_level, 'Rueda de Inferencias: Marie Curie', 'Gira la ruleta y escribe inferencias sobre los fragmentos de texto.', 'Lee cada fragmento y escribe una inferencia que demuestre comprensión profunda.', -- CONFIG: Configuración visual y de comportamiento '{ "wheelAnimation": true, "showTimer": true, "timerSeconds": 30, "allowSkip": false, "minCharacters": 20, "maxCharacters": 200 }'::jsonb, -- CONTENT: Lo que ve el estudiante '{ "categories": [ { "id": "cat-literal", "name": "Literal", "color": "#4ECDC4", "icon": "📖" }, { "id": "cat-inferencial", "name": "Inferencial", "color": "#FF6B6B", "icon": "🔍" }, { "id": "cat-critico", "name": "Crítico", "color": "#95E1D3", "icon": "💡" }, { "id": "cat-creativo", "name": "Creativo", "color": "#F38181", "icon": "🎨" } ], "fragments": [ { "id": "frag-1", "text": "Marie Curie fue la primera mujer en ganar un Premio Nobel..." }, { "id": "frag-2", "text": "A pesar de los obstáculos de su época..." }, { "id": "frag-3", "text": "Sus cuadernos de laboratorio siguen siendo radiactivos..." } ], "settings": { "timerSeconds": 30, "minLength": 20, "maxLength": 200 } }'::jsonb, -- SOLUTION: Keywords para validación '{ "validation": { "minKeywords": 2, "minLength": 20, "maxLength": 200 }, "fragments": [ { "id": "frag-1", "keywords": ["pionera", "radiactividad", "nobel", "primera", "mujer", "cientifico", "premio"], "points": 20 }, { "id": "frag-2", "keywords": ["determinacion", "obstaculos", "epoca", "primera", "catedratica", "superacion"], "points": 20 }, { "id": "frag-3", "keywords": ["cuadernos", "radiactivos", "legado", "cien", "anos", "historia", "impacto"], "points": 20 } ] }'::jsonb, -- RUBRIC '{ "criteria": [ { "name": "Relevancia", "weight": 0.5, "description": "Uso de conceptos clave del texto" }, { "name": "Longitud", "weight": 0.3, "description": "Cumplimiento de extensión requerida" }, { "name": "Coherencia", "weight": 0.2, "description": "Texto comprensible y bien estructurado" } ] }'::jsonb, 180, -- time_limit_seconds (3 minutos total) 50, -- xp_reward 20, -- ml_coins_reward 5, -- order_index true -- is_active ); ``` --- ## 9. Guía para Crear Keywords Efectivos ### 9.1 Principios 1. **Conceptos clave del texto**: Incluir sustantivos y verbos importantes 2. **Sin acentos**: Las keywords deben estar normalizadas (sin acentos) 3. **Variedad**: Incluir sinónimos para dar flexibilidad 4. **Específicos al tema**: Evitar palabras muy genéricas ### 9.2 Ejemplo de Buenos Keywords Para un fragmento sobre Marie Curie: - ✅ "pionera", "radiactividad", "nobel", "polonio", "radio", "descubrimiento" - ❌ "importante", "famosa", "científica" (muy genéricos) ### 9.3 Cantidad Recomendada - **Mínimo**: 5 keywords por fragmento - **Recomendado**: 7-10 keywords por fragmento - **Máximo**: 15 keywords por fragmento --- ## 10. Trazabilidad | Documento | Relación | |-----------|----------| | DB-071 | Función SQL de validación | | BE-FE-071 | DTO de backend | | FE-071 | Componente de frontend | | US-ACT-005 | Historia de usuario | --- ## 11. Historial de Versiones | Versión | Fecha | Cambios | |---------|-------|---------| | 1.0 | 2025-11-21 | Versión inicial | --- **Autor:** Frontend Agent (NEXUS-FRONTEND) **Revisado por:** - **Estado:** ✅ APROBADO