Some checks are pending
CI Pipeline / changes (push) Waiting to run
CI Pipeline / core (push) Blocked by required conditions
CI Pipeline / trading-backend (push) Blocked by required conditions
CI Pipeline / trading-data-service (push) Blocked by required conditions
CI Pipeline / trading-frontend (push) Blocked by required conditions
CI Pipeline / erp-core (push) Blocked by required conditions
CI Pipeline / erp-mecanicas (push) Blocked by required conditions
CI Pipeline / gamilit-backend (push) Blocked by required conditions
CI Pipeline / gamilit-frontend (push) Blocked by required conditions
Gamilit: - Backend: Teacher services, assignments, gamification, exercise submissions - Frontend: Admin/Teacher/Student portals, module 4-5 mechanics, monitoring - Database: DDL functions, seeds for dev/prod, auth/gamification schemas - Docs: Architecture, features, guides cleanup and reorganization Core/Orchestration: - New workspace directives index - Documentation directive Trading-platform: - Database seeds and inventory updates - Tech leader validation report 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
13 KiB
13 KiB
PLAN DE IMPLEMENTACIÓN: Corrección Ejercicio 5 Módulo 2
Fecha: 2025-12-15 Rol: Tech Leader Proyecto: Gamilit Referencia: ANALISIS-EJERCICIO-M2E5-M3-2025-12-15.md
RESUMEN EJECUTIVO
Problema: La función SQL validate_rueda_inferencias() no maneja la estructura categoryExpectations del seed, resultando en score = 0.
Solución: Modificar la función SQL para soportar ambas estructuras (flat legacy y categoryExpectations nueva).
CAMBIOS REQUERIDOS
1. Modificar Función SQL validate_rueda_inferencias()
Archivo: ddl/schemas/educational_content/functions/14-validate_rueda_inferencias.sql
Cambio Principal: En el loop de validación (líneas 262-275), agregar lógica para:
- Detectar si el fragment tiene
categoryExpectations - Obtener
categoryIddelfragmentStatesenviado por el frontend - Extraer keywords y points de la categoría correcta
2. Lógica de Corrección
ANTES (líneas 262-264):
v_keywords := v_fragment_solution->'keywords'; -- NULL
v_fragment_points := COALESCE(v_fragment_solution->>'points', 20); -- 20 default
DESPUÉS:
IF v_fragment_solution ? 'categoryExpectations' THEN
-- Buscar categoryId en fragmentStates
v_category_id := obtener de fragmentStates donde fragmentId = v_fragment_id
-- Extraer keywords y points de categoryExpectations[categoryId]
v_keywords := v_fragment_solution->'categoryExpectations'->v_category_id->'keywords';
v_fragment_points := v_fragment_solution->'categoryExpectations'->v_category_id->>'points';
ELSE
-- Estructura flat legacy
v_keywords := v_fragment_solution->'keywords';
v_fragment_points := v_fragment_solution->>'points';
END IF;
FUNCIÓN SQL CORREGIDA
-- ============================================================================
-- FUNCIÓN: validate_rueda_inferencias (CORREGIDA v2)
-- Descripción: Soporta estructura categoryExpectations + flat legacy
-- Autor: Tech Leader
-- Fecha: 2025-12-15
-- Ticket: ANALISIS-EJERCICIO-M2E5-M3-2025-12-15
-- ============================================================================
CREATE OR REPLACE FUNCTION educational_content.validate_rueda_inferencias(
p_solution JSONB,
p_submitted_answer JSONB,
p_max_points INTEGER,
p_allow_partial_credit BOOLEAN DEFAULT true,
p_normalize_text BOOLEAN DEFAULT true,
OUT is_correct BOOLEAN,
OUT score INTEGER,
OUT feedback TEXT,
OUT details JSONB
)
RETURNS RECORD
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_validation JSONB;
v_min_keywords INTEGER;
v_min_length INTEGER;
v_max_length INTEGER;
v_fragments_solution JSONB;
v_fragments_submitted JSONB;
v_fragment_states JSONB;
v_fragment_id TEXT;
v_user_text TEXT;
v_fragment_solution JSONB;
v_keywords JSONB;
v_fragment_points INTEGER;
v_category_id TEXT;
v_fragment_state JSONB;
v_validation_result JSONB;
v_total_fragments INTEGER := 0;
v_valid_fragments INTEGER := 0;
v_total_points INTEGER := 0;
v_accumulated_score INTEGER := 0;
v_results JSONB := '[]'::jsonb;
v_feedback_parts TEXT[] := ARRAY[]::TEXT[];
BEGIN
-- 1. Validar estructura de entrada
IF p_solution IS NULL OR p_submitted_answer IS NULL THEN
RAISE EXCEPTION 'Invalid input: solution and submitted_answer are required';
END IF;
v_fragments_submitted := p_submitted_answer->'fragments';
v_fragment_states := p_submitted_answer->'fragmentStates';
IF v_fragments_submitted IS NULL THEN
RAISE EXCEPTION 'Invalid submitted_answer format: missing "fragments" object';
END IF;
-- 2. Extraer criterios de validación globales
v_validation := p_solution->'validation';
v_min_keywords := COALESCE((v_validation->>'minKeywords')::INTEGER, 2);
v_min_length := COALESCE((v_validation->>'minLength')::INTEGER, 20);
v_max_length := COALESCE((v_validation->>'maxLength')::INTEGER, 200);
v_fragments_solution := p_solution->'fragments';
IF v_fragments_solution IS NULL THEN
RAISE EXCEPTION 'Invalid solution format: missing "fragments" array';
END IF;
-- 3. Iterar sobre cada fragmento enviado por el usuario
FOR v_fragment_id, v_user_text IN
SELECT key, value::text
FROM jsonb_each_text(v_fragments_submitted)
LOOP
v_total_fragments := v_total_fragments + 1;
-- Buscar la solución para este fragmento
SELECT fragment INTO v_fragment_solution
FROM jsonb_array_elements(v_fragments_solution) AS fragment
WHERE fragment->>'id' = v_fragment_id;
IF v_fragment_solution IS NULL THEN
-- Fragmento no encontrado en solution
v_results := v_results || jsonb_build_object(
'fragment_id', v_fragment_id,
'is_valid', false,
'error', 'Fragment not found in solution',
'points', 0
);
CONTINUE;
END IF;
-- ====================================================================
-- FIX 2025-12-15: Soporte para estructura categoryExpectations
-- ====================================================================
IF v_fragment_solution ? 'categoryExpectations' THEN
-- Nueva estructura con categorías
-- Buscar categoryId en fragmentStates para este fragment
v_category_id := 'cat-literal'; -- default fallback
IF v_fragment_states IS NOT NULL THEN
SELECT state INTO v_fragment_state
FROM jsonb_array_elements(v_fragment_states) AS state
WHERE state->>'fragmentId' = v_fragment_id;
IF v_fragment_state IS NOT NULL THEN
v_category_id := COALESCE(v_fragment_state->>'categoryId', 'cat-literal');
END IF;
END IF;
-- Extraer keywords y points de categoryExpectations[categoryId]
v_keywords := v_fragment_solution->'categoryExpectations'->v_category_id->'keywords';
v_fragment_points := COALESCE(
(v_fragment_solution->'categoryExpectations'->v_category_id->>'points')::INTEGER,
20
);
-- Log para debug (comentar en producción)
RAISE NOTICE '[FIX] Fragment % using category % with % keywords',
v_fragment_id, v_category_id, jsonb_array_length(COALESCE(v_keywords, '[]'::jsonb));
ELSE
-- Estructura flat legacy
v_keywords := v_fragment_solution->'keywords';
v_fragment_points := COALESCE((v_fragment_solution->>'points')::INTEGER, 20);
END IF;
-- ====================================================================
-- END FIX
-- ====================================================================
v_total_points := v_total_points + v_fragment_points;
-- Validar que v_keywords no sea NULL
IF v_keywords IS NULL THEN
v_results := v_results || jsonb_build_object(
'fragment_id', v_fragment_id,
'is_valid', false,
'error', format('Keywords not found for fragment %s (category: %s)', v_fragment_id, v_category_id),
'points', 0,
'category_used', v_category_id
);
CONTINUE;
END IF;
-- Validar el fragmento usando la función auxiliar
v_validation_result := educational_content._validate_single_fragment(
v_keywords,
v_min_keywords,
v_min_length,
v_max_length,
v_user_text,
v_fragment_points
);
-- Acumular resultados
IF (v_validation_result->>'is_valid')::boolean THEN
v_valid_fragments := v_valid_fragments + 1;
END IF;
v_accumulated_score := v_accumulated_score + (v_validation_result->>'points')::integer;
-- Agregar resultado del fragmento al detalle
v_results := v_results || jsonb_build_object(
'fragment_id', v_fragment_id,
'category_used', v_category_id,
'is_valid', v_validation_result->'is_valid',
'matched_keywords', v_validation_result->'matched_keywords',
'keyword_count', v_validation_result->'keyword_count',
'points', v_validation_result->'points',
'max_points', v_fragment_points,
'feedback', v_validation_result->'feedback'
);
END LOOP;
-- 4. Calcular puntuación final
IF p_allow_partial_credit THEN
-- Ajustar a p_max_points (normalmente 100)
IF v_total_points > 0 THEN
score := ROUND((v_accumulated_score::NUMERIC / v_total_points) * p_max_points);
ELSE
score := 0;
END IF;
ELSE
score := CASE
WHEN v_valid_fragments = v_total_fragments THEN p_max_points
ELSE 0
END;
END IF;
-- 5. Determinar si es correcto (al menos 70% de fragmentos válidos)
is_correct := (score >= 70);
-- 6. Generar feedback
IF v_valid_fragments = v_total_fragments THEN
feedback := format('¡Excelente! Todas las %s inferencias son válidas. Has demostrado comprensión profunda del texto.',
v_total_fragments);
ELSIF v_valid_fragments > 0 THEN
feedback := format('%s de %s inferencias válidas (Puntuación: %s%%). Revisa los fragmentos marcados para mejorar tu respuesta.',
v_valid_fragments, v_total_fragments, score);
ELSE
feedback := format('Ninguna inferencia válida. Asegúrate de incluir conceptos clave del texto y cumplir con la longitud requerida (%s-%s caracteres).',
v_min_length, v_max_length);
END IF;
-- 7. Construir detalles
details := jsonb_build_object(
'total_fragments', v_total_fragments,
'valid_fragments', v_valid_fragments,
'total_points_possible', v_total_points,
'points_earned', v_accumulated_score,
'percentage', score,
'validation_criteria', jsonb_build_object(
'min_keywords', v_min_keywords,
'min_length', v_min_length,
'max_length', v_max_length
),
'results_per_fragment', v_results
);
END;
$$;
COMMENT ON FUNCTION educational_content.validate_rueda_inferencias IS
'Wrapper estándar para validación de rueda de inferencias.
Soporta DOS estructuras de solución:
1. NUEVA (categoryExpectations): { fragments: [{ id, categoryExpectations: { cat-xxx: { keywords, points } } }] }
2. LEGACY (flat): { fragments: [{ id, keywords, points }] }
El frontend envía fragmentStates con categoryId para indicar qué categoría usó el usuario.
FIX 2025-12-15: Corregido para manejar estructura categoryExpectations del seed.';
PASOS DE IMPLEMENTACIÓN
Paso 1: Backup de función actual
-- Crear backup antes de modificar
CREATE OR REPLACE FUNCTION educational_content.validate_rueda_inferencias_backup_20251215
AS $$ ... $$ -- copia exacta de la función actual
Paso 2: Ejecutar función corregida
# Conectar a la base de datos
PGPASSWORD=xxx psql -h localhost -U gamilit_user -d gamilit_platform
# Ejecutar el script de la función corregida
\i /path/to/corrected_function.sql
Paso 3: Prueba de validación
-- Test con datos reales del seed
SELECT * FROM educational_content.validate_rueda_inferencias(
-- solution (del seed)
'{
"validation": { "minKeywords": 2, "minLength": 20, "maxLength": 200 },
"fragments": [{
"id": "frag-1",
"categoryExpectations": {
"cat-literal": { "keywords": ["pionera", "radiactividad", "nobel"], "points": 20 }
}
}]
}'::jsonb,
-- submitted_answer (del frontend)
'{
"fragments": { "frag-1": "Marie Curie fue pionera en la radiactividad" },
"fragmentStates": [{ "fragmentId": "frag-1", "categoryId": "cat-literal" }]
}'::jsonb,
100,
true,
true
);
-- Resultado esperado: score > 0, is_correct = true
Paso 4: Test E2E
- Abrir frontend
- Ir a Módulo 2, Ejercicio 5
- Completar el ejercicio con texto válido
- Verificar que score > 0 y XP/ML Coins se asignan
VALIDACIÓN DE DEPENDENCIAS
Funciones que llaman a validate_rueda_inferencias()
| Función | Ubicación | Impacto |
|---|---|---|
validate_answer() |
02-validate_answer.sql:169-177 |
Sin cambio - firma igual |
validate_and_audit() |
20-validate_and_audit.sql:94-98 |
Sin cambio - usa validate_answer() |
Cambios en Firma de Función
NO HAY CAMBIOS EN LA FIRMA:
- Input:
(p_solution JSONB, p_submitted_answer JSONB, p_max_points INTEGER, ...) - Output:
(is_correct BOOLEAN, score INTEGER, feedback TEXT, details JSONB)
La firma se mantiene idéntica, solo cambia la lógica interna.
ROLLBACK
Si hay problemas, ejecutar:
-- Restaurar función original desde backup
DROP FUNCTION educational_content.validate_rueda_inferencias;
ALTER FUNCTION educational_content.validate_rueda_inferencias_backup_20251215
RENAME TO validate_rueda_inferencias;
CHECKLIST DE VALIDACIÓN
- Backup de función original creado
- Función corregida ejecutada sin errores
- Test SQL directo pasa (score > 0)
- Test E2E ejercicio 2.5 pasa
- XP se asigna correctamente
- ML Coins se asignan correctamente
- Otros ejercicios M2 siguen funcionando (regression test)