- 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>
8.7 KiB
8.7 KiB
US-ACT-003: Mecánicas básicas - Completar texto
Épica: EAI-002 - Actividades Básicas Hardcodeadas Sprint: Mes 1, Semana 3 Story Points: 5 SP Presupuesto: $1,800 MXN Prioridad: Alta (Alcance Inicial) Estado: ✅ Completada (Mes 1)
Descripción
Como estudiante, quiero completar espacios en blanco en oraciones o párrafos para demostrar que conozco términos y conceptos específicos.
Contexto del Alcance Inicial: Mecánica que requiere input de texto libre. El sistema valida la respuesta contra múltiples respuestas correctas posibles (sinónimos, variaciones). Hardcodeada en BD.
Criterios de Aceptación
- CA-01: Se muestra un texto con espacios en blanco (marcados con ___)
- CA-02: Se proporciona un campo de texto para cada espacio
- CA-03: El estudiante escribe su respuesta
- CA-04: La validación es case-insensitive
- CA-05: Se aceptan múltiples respuestas correctas (sinónimos)
- CA-06: Se ignoran espacios extra al inicio/final
- CA-07: Se muestra feedback con la(s) respuesta(s) correcta(s)
- CA-08: Se otorgan XP y monedas
- CA-09: Se permite ver la respuesta correcta si falla
Especificaciones Técnicas
Backend
Content Type:
interface FillBlankContent {
text: string // "Los mayas usaban un sistema numérico ___"
blanks: Array<{
id: string
position: number // Posición del blank en el texto
acceptedAnswers: string[] // ['vigesimal', 'de base 20', 'base 20']
}>
explanation: string
}
Validación:
class ActivitiesService {
private validateFillBlank(
content: FillBlankContent,
userAnswer: { answers: Record<string, string> } // { blank1: 'vigesimal' }
): boolean {
return content.blanks.every(blank => {
const userInput = userAnswer.answers[blank.id]?.trim().toLowerCase()
return blank.acceptedAnswers.some(
accepted => accepted.toLowerCase() === userInput
)
})
}
}
Frontend
Componente:
// components/activities/FillBlankActivity.tsx
export function FillBlankActivity({ activity, onComplete }) {
const [answers, setAnswers] = useState<Record<string, string>>({})
const [submitted, setSubmitted] = useState(false)
const [feedback, setFeedback] = useState(null)
const renderTextWithBlanks = () => {
const parts = activity.content.text.split('___')
return parts.map((part, index) => (
<span key={index}>
{part}
{index < parts.length - 1 && (
<input
type="text"
value={answers[`blank${index}`] || ''}
onChange={(e) => setAnswers({
...answers,
[`blank${index}`]: e.target.value
})}
disabled={submitted}
className={`inline-block mx-2 px-3 py-1 border-b-2 ${
submitted
? feedback.isCorrect
? 'border-green-500 bg-green-50'
: 'border-red-500 bg-red-50'
: 'border-maya-green-500 focus:border-maya-green-700'
}`}
placeholder="___"
/>
)}
</span>
))
}
return (
<Card>
<h2>{activity.title}</h2>
<div className="text-lg leading-relaxed mb-6">
{renderTextWithBlanks()}
</div>
{!submitted ? (
<Button onClick={handleSubmit}>
Verificar
</Button>
) : (
<FeedbackSection
feedback={feedback}
correctAnswers={activity.content.blanks}
onContinue={onComplete}
/>
)}
</Card>
)
}
Seed Data
const fillBlankActivities = [
{
moduleId: 'modulo-numeros-mayas',
title: 'Sistema numérico maya',
type: ActivityType.FILL_BLANK,
content: {
text: 'Los mayas desarrollaron un sistema numérico ___, es decir, basado en el número ___. En este sistema, una barra representa el número ___ y un punto representa ___.',
blanks: [
{ id: 'blank0', position: 0, acceptedAnswers: ['vigesimal', 'de base 20', 'base 20'] },
{ id: 'blank1', position: 1, acceptedAnswers: ['20', 'veinte'] },
{ id: 'blank2', position: 2, acceptedAnswers: ['5', 'cinco'] },
{ id: 'blank3', position: 3, acceptedAnswers: ['1', 'uno', 'la unidad'] }
],
explanation: 'El sistema vigesimal maya (base 20) utiliza barras (valor 5) y puntos (valor 1) para representar números.'
},
order: 3,
xpReward: 15,
coinsReward: 7
}
]
Dependencias
Antes:
- US-ACT-001 (Infraestructura base)
Después:
- Amplía variedad de mecánicas educativas
Definición de Hecho (DoD)
- Validación con múltiples respuestas aceptadas
- Case-insensitive validation
- Componente con inputs inline
- Feedback muestra respuestas correctas
- Seed data con 5+ actividades
- Tests de validación
Notas del Alcance Inicial
- ✅ Validación simple (igualdad exacta)
- ✅ Sin validación semántica con IA
- ✅ Sin autocompletado
- ⚠️ Extensión futura: EXT-018-AIValidation (validación semántica con NLP)
Estimación
Desglose (5 SP = ~1.75 días):
- Backend: validación: 0.5 días
- Frontend: inputs inline: 0.75 días
- Seed data: 0.25 días
- Testing: 0.25 días
Tareas de Implementación
Backend (8h - 40%)
1. Tipos y Estructuras (2.4h)
- 1.1 Definir interfaz
FillBlankContentcon: text, blanks[], explanation (0.5h) - 1.2 Definir tipo
Blankcon: id, position, acceptedAnswers[] (0.4h) - 1.3 Crear tipo para
userAnswer: Record<string, string> (0.3h) - 1.4 Extender enum ActivityType con
FILL_BLANK(0.2h) - 1.5 Crear DTOs de validación con class-validator (0.6h)
- 1.6 Documentar estructura en README técnico (0.4h)
2. Lógica de Validación (3.6h)
- 2.1 Implementar método
validateFillBlank()con case-insensitive (1.2h) - 2.2 Implementar normalización de strings (trim, toLowerCase) (0.6h)
- 2.3 Implementar validación de múltiples respuestas aceptadas (0.8h)
- 2.4 Agregar lógica para verificar todos los blanks completados (0.5h)
- 2.5 Implementar extracción de respuestas correctas para feedback (0.5h)
3. Integración y Tests (2h)
- 3.1 Integrar validación FILL_BLANK en
submitAnswer()(0.6h) - 3.2 Test unitario: validación case-insensitive (0.4h)
- 3.3 Test unitario: múltiples respuestas aceptadas (0.4h)
- 3.4 Test: validación con espacios extra (0.3h)
- 3.5 Test integración: endpoint con fill-blank activity (0.3h)
Frontend (7h - 35%)
4. Componente Principal (4.2h)
- 4.1 Crear componente
FillBlankActivity.tsxcon estructura base (0.8h) - 4.2 Implementar estado: answers (Record), submitted, feedback (0.5h)
- 4.3 Implementar función
renderTextWithBlanks()para parsear "___" (1.5h) - 4.4 Crear inputs inline con estilos dinámicos (correcto/incorrecto) (1h)
- 4.5 Integrar componente FeedbackSection con respuestas correctas (0.4h)
5. Interactividad y UX (2.8h)
- 5.1 Implementar lógica de actualización de answers con onChange (0.6h)
- 5.2 Agregar placeholders visuales para inputs vacíos (0.4h)
- 5.3 Deshabilitar inputs tras envío (0.3h)
- 5.4 Implementar autofocus en primer input (0.3h)
- 5.5 Agregar contador de blanks completados / total (0.5h)
- 5.6 Implementar responsive: inputs apilados en mobile (0.4h)
- 5.7 Agregar animaciones de feedback (0.3h)
Testing y QA (4h - 20%)
6. Tests Backend (2h)
- 6.1 Test: validación correcta con respuesta exacta (0.4h)
- 6.2 Test: validación case-insensitive (0.3h)
- 6.3 Test: aceptación de sinónimos (0.4h)
- 6.4 Test: rechazo de respuesta incorrecta (0.3h)
- 6.5 Test: validación con espacios extra (0.3h)
- 6.6 Test: validación de múltiples blanks (0.3h)
7. Tests Frontend (2h)
- 7.1 Test: renderizado de texto con blanks (0.5h)
- 7.2 Test: actualización de estado al escribir (0.4h)
- 7.3 Test: feedback muestra respuestas correctas (0.4h)
- 7.4 Test: deshabilitar inputs tras envío (0.3h)
- 7.5 Test E2E: flujo completo fill-blank (0.4h)
Deploy y Datos (1h - 5%)
8. Seed Data (1h)
- 8.1 Crear 5+ actividades de completar texto sobre cultura Maya (0.6h)
- 8.2 Definir múltiples respuestas aceptadas por blank (sinónimos) (0.3h)
- 8.3 Validar coherencia de textos y respuestas (0.1h)
Total Estimado: 20h (5 SP × 4h) Desglose: Backend 40% | Frontend 35% | Testing 20% | Deploy 5%
Creado: 2025-11-02 Responsable: Equipo Fullstack