# US-ACT-006: Mecánicas intermedias - Asociación **Épica:** EAI-002 - Actividades Básicas Hardcodeadas **Sprint:** Mes 1, Semana 4 **Story Points:** 7 SP **Presupuesto:** $2,600 MXN **Prioridad:** Alta (Alcance Inicial) **Estado:** ✅ Completada (Mes 1) --- ## Descripción Como **estudiante**, quiero **conectar elementos relacionados** para **demostrar comprensión de relaciones entre conceptos**. **Contexto del Alcance Inicial:** Mecánica de matching/emparejamiento. El estudiante conecta items de columna A con columna B mediante líneas o clicks. Hardcodeada en BD. --- ## Criterios de Aceptación - [ ] **CA-01:** Se muestran dos columnas de items - [ ] **CA-02:** El estudiante hace click en un item de cada columna para emparejar - [ ] **CA-03:** Se muestra línea visual conectando los pares - [ ] **CA-04:** Se puede deshacer un emparejamiento - [ ] **CA-05:** Validación de todos los pares - [ ] **CA-06:** Feedback muestra pares correctos/incorrectos - [ ] **CA-07:** Responsive en mobile (sin líneas, usa botones) --- ## Especificaciones Técnicas ### Backend ```typescript interface MatchingContent { instructions: string leftColumn: Array<{ id: string content: string }> rightColumn: Array<{ id: string content: string }> correctPairs: Array<{ leftId: string rightId: string }> explanation: string } // Validación private validateMatching( content: MatchingContent, userAnswer: { pairs: Array<{ leftId: string, rightId: string }> } ): boolean { if (userAnswer.pairs.length !== content.correctPairs.length) return false return content.correctPairs.every(correctPair => userAnswer.pairs.some( userPair => userPair.leftId === correctPair.leftId && userPair.rightId === correctPair.rightId ) ) } ``` ### Frontend ```typescript export function MatchingActivity({ activity }) { const [pairs, setPairs] = useState>([]) const [selectedLeft, setSelectedLeft] = useState(null) const [selectedRight, setSelectedRight] = useState(null) const handleLeftClick = (id: string) => { setSelectedLeft(id) if (selectedRight) { addPair(id, selectedRight) setSelectedLeft(null) setSelectedRight(null) } } const handleRightClick = (id: string) => { setSelectedRight(id) if (selectedLeft) { addPair(selectedLeft, id) setSelectedLeft(null) setSelectedRight(null) } } const addPair = (leftId: string, rightId: string) => { // Remove existing pairs with these items const filteredPairs = pairs.filter( p => p.leftId !== leftId && p.rightId !== rightId ) setPairs([...filteredPairs, { leftId, rightId }]) } return (

{activity.title}

{activity.content.instructions}

{/* Left Column */}
{activity.content.leftColumn.map(item => { const isPaired = pairs.some(p => p.leftId === item.id) const isSelected = selectedLeft === item.id return ( ) })}
{/* Right Column */}
{activity.content.rightColumn.map(item => { const isPaired = pairs.some(p => p.rightId === item.id) const isSelected = selectedRight === item.id return ( ) })}
{/* Pares actuales */} {pairs.length > 0 && (

Pares creados:

{pairs.map((pair, index) => { const left = activity.content.leftColumn.find(i => i.id === pair.leftId) const right = activity.content.rightColumn.find(i => i.id === pair.rightId) return (
{left?.content} {right?.content}
) })}
)} {!submitted && ( )}
) } ``` ### Seed Data ```typescript { title: 'Relacionar números mayas con valores', type: ActivityType.MATCHING, content: { instructions: 'Conecta cada símbolo maya con su valor decimal', leftColumn: [ { id: 'l1', content: '● (un punto)' }, { id: 'l2', content: '━ (una barra)' }, { id: 'l3', content: '●●● (tres puntos)' }, { id: 'l4', content: '━━ (dos barras)' }, ], rightColumn: [ { id: 'r1', content: '10' }, { id: 'r2', content: '1' }, { id: 'r3', content: '5' }, { id: 'r4', content: '3' }, ], correctPairs: [ { leftId: 'l1', rightId: 'r2' }, // 1 punto = 1 { leftId: 'l2', rightId: 'r3' }, // 1 barra = 5 { leftId: 'l3', rightId: 'r4' }, // 3 puntos = 3 { leftId: 'l4', rightId: 'r1' }, // 2 barras = 10 ], explanation: 'Cada punto vale 1 y cada barra vale 5 en el sistema numérico maya.' }, xpReward: 18, coinsReward: 8 } ``` --- ## Dependencias **Antes:** US-ACT-001 (Infraestructura) --- ## Definición de Hecho (DoD) - [x] Emparejamiento funcional - [x] Feedback visual - [x] Validación de pares - [x] Deshacer pares - [x] Seed data con 5+ actividades - [x] Responsive --- ## Notas - ✅ Sin dibujar líneas (simplificado con clicks) - ⚠️ **Extensión futura:** EXT-019 (líneas SVG conectando pares) --- ## Estimación **Desglose (7 SP = ~2.5 días):** - Backend: 0.5 días - Frontend: 1.5 días - Testing: 0.5 días --- ## 💻 Implementación Técnica ### Validadores Relacionados Esta historia de usuario (Asociación/Matching) se implementa en diferentes variantes: | Tipo de Ejercicio | Validador DB | Formato Respuesta | Descripción | |-------------------|-------------|-------------------|-------------| | `rueda_inferencias` | `validate_rueda_inferencias()` | `{"inferences": {"inf1": "conclusion1", "inf2": "conclusion2"}}` | Matching de inferencias (1 a 1) | | `construccion_hipotesis` | `validate_cause_effect_matching()` | `{"causes": {"c1": ["cons1", "cons2"], "c2": ["cons3"]}}` | **Matching causa-efecto drag & drop (1 a muchos)** | ### Implementación Causa-Efecto (construccion_hipotesis) **Componente Frontend:** `CausaEfectoExercise.tsx` **Ubicación:** `apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/` **Validador DB:** `validate_cause_effect_matching()` **Tipo en ENUM:** `construccion_hipotesis` **Formato de respuesta:** ```json { "causes": { "cause-1": ["consequence-a", "consequence-b"], "cause-2": ["consequence-c"], "cause-3": ["consequence-d", "consequence-e", "consequence-f"] } } ``` **Formato de solución:** ```json { "correctMatches": { "cause-1": ["consequence-a", "consequence-b"], "cause-2": ["consequence-c"], "cause-3": ["consequence-d", "consequence-e", "consequence-f"] }, "allowPartialMatches": true, "strictOrder": false } ``` **Características:** - ✅ Drag & drop de consecuencias hacia causas - ✅ Múltiples consecuencias por causa (1 a muchos) - ✅ Orden flexible (configurable con `strictOrder`) - ✅ Crédito parcial por matches correctos - ✅ Feedback detallado por causa con errores específicos - ✅ Score proporcional: `(consecuencias correctas / total) × max_points` **Diferencia vs. Rueda Inferencias:** - **Rueda Inferencias:** Matching 1-a-1 (una inferencia → una conclusión) - **Causa-Efecto:** Matching 1-a-muchos (una causa → múltiples consecuencias) **Implementado en:** FE-059, DB-117, DB-123 (2025-11-19) **Referencias:** - **Handoff:** `orchestration/HANDOFF-FE-059-TO-DB.md` - Discrepancia 3 - **Validador:** `apps/database/ddl/schemas/educational_content/functions/22-validate_cause_effect_matching.sql` - **Especificación:** `orchestration/SQL-SPECS-NUEVOS-VALIDADORES-FE-059.md` - **Seeds Testing:** `apps/database/seeds/dev/educational_content/10-test-nuevos-validadores-FE-059.sql` **Beneficio:** Permite modelar relaciones causa-efecto complejas del mundo real donde una causa puede tener múltiples consecuencias, mejorando el aprendizaje crítico. --- **Creado:** 2025-11-02 **Actualizado:** 2025-11-19 - DB-123 (Agregada sección de implementación técnica) **Responsable:** Equipo Fullstack