workspace/projects/gamilit/docs/01-fase-alcance-inicial/EAI-002-actividades/historias-usuario/US-ACT-006-mecanica-asociacion.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00

9.8 KiB
Raw Permalink Blame History

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