Structure: - control-plane/: Registries, SIMCO directives, CI/CD templates - projects/: Gamilit, ERP-Suite, Trading-Platform, Betting-Analytics - shared/: Libs catalog, knowledge-base Key features: - Centralized port, domain, database, and service registries - 23 SIMCO directives + 6 fundamental principles - NEXUS agent profiles with delegation rules - Validation scripts for workspace integrity - Dockerfiles for all services - Path aliases for quick reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.8 KiB
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
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
export function MatchingActivity({ activity }) {
const [pairs, setPairs] = useState<Array<{ leftId: string, rightId: string }>>([])
const [selectedLeft, setSelectedLeft] = useState<string | null>(null)
const [selectedRight, setSelectedRight] = useState<string | null>(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 (
<Card>
<h2>{activity.title}</h2>
<p>{activity.content.instructions}</p>
<div className="grid grid-cols-2 gap-8 my-6">
{/* Left Column */}
<div className="space-y-3">
{activity.content.leftColumn.map(item => {
const isPaired = pairs.some(p => p.leftId === item.id)
const isSelected = selectedLeft === item.id
return (
<button
key={item.id}
onClick={() => handleLeftClick(item.id)}
className={`w-full p-4 text-left rounded-lg border-2 transition-colors ${
isSelected ? 'border-maya-green-500 bg-maya-green-50' :
isPaired ? 'border-green-300 bg-green-50' :
'border-gray-300 hover:border-maya-green-300'
}`}
>
{item.content}
</button>
)
})}
</div>
{/* Right Column */}
<div className="space-y-3">
{activity.content.rightColumn.map(item => {
const isPaired = pairs.some(p => p.rightId === item.id)
const isSelected = selectedRight === item.id
return (
<button
key={item.id}
onClick={() => handleRightClick(item.id)}
className={`w-full p-4 text-left rounded-lg border-2 transition-colors ${
isSelected ? 'border-maya-green-500 bg-maya-green-50' :
isPaired ? 'border-green-300 bg-green-50' :
'border-gray-300 hover:border-maya-green-300'
}`}
>
{item.content}
</button>
)
})}
</div>
</div>
{/* Pares actuales */}
{pairs.length > 0 && (
<div className="mb-4">
<h3 className="font-medium mb-2">Pares creados:</h3>
<div className="space-y-1">
{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 (
<div key={index} className="flex items-center gap-2 text-sm">
<span>{left?.content}</span>
<span className="text-maya-green-500">↔</span>
<span>{right?.content}</span>
<button
onClick={() => setPairs(pairs.filter((_, i) => i !== index))}
className="ml-auto text-red-500 hover:text-red-700"
>
✕
</button>
</div>
)
})}
</div>
</div>
)}
{!submitted && (
<Button
onClick={handleSubmit}
disabled={pairs.length !== activity.content.correctPairs.length}
>
Verificar
</Button>
)}
</Card>
)
}
Seed Data
{
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)
- Emparejamiento funcional
- Feedback visual
- Validación de pares
- Deshacer pares
- Seed data con 5+ actividades
- 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:
{
"causes": {
"cause-1": ["consequence-a", "consequence-b"],
"cause-2": ["consequence-c"],
"cause-3": ["consequence-d", "consequence-e", "consequence-f"]
}
}
Formato de solución:
{
"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