Skip to content

Commit e04043d

Browse files
authored
Merge pull request #35 from No-Country-simulation/Curosfinalfrontend
Se ajustaron detalles de las validaciones de los cursos tanto en back…
2 parents 6b78095 + 0450497 commit e04043d

7 files changed

Lines changed: 620 additions & 39 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Backend Contract: Technical Test Generation Context
2+
3+
Fecha: 2026-05-28
4+
5+
## Objetivo
6+
7+
Hacer que la prueba tecnica del talento se genere usando:
8+
9+
- `technicalSkills` del perfil
10+
- `professionalArea` o `headline`
11+
- `interestedRoles` como contexto adicional
12+
13+
Frontend ya envia ese contexto y mantiene compatibilidad con el contrato anterior si backend todavia no lo soporta.
14+
15+
## Endpoint principal
16+
17+
### POST `/assessments/me/generate-tests`
18+
19+
Debe aceptar payload vacio o contextual.
20+
21+
### Request DTO sugerido
22+
23+
```ts
24+
interface GenerateTestsForProfileDto {
25+
technicalSkills?: string[];
26+
professionalArea?: string;
27+
headline?: string;
28+
interestedRoles?: string[];
29+
}
30+
```
31+
32+
## Regla funcional esperada
33+
34+
- Si `technicalSkills` tiene datos, las pruebas tecnicas deben priorizar preguntas alineadas con esas skills.
35+
- Si `professionalArea` o `headline` tiene datos, deben usarse para refinar el enfoque de la prueba.
36+
- Si no llega contexto, backend debe seguir funcionando con el comportamiento actual.
37+
- Si llega contexto parcial, backend debe usar lo disponible sin fallar.
38+
39+
## Endpoint secundario recomendado
40+
41+
### GET `/assessments/technical-tests/questions`
42+
43+
Compatibilidad recomendada con query params:
44+
45+
- `technicalSkills=React,TypeScript,Node.js`
46+
- `professionalArea=Frontend Developer`
47+
- `headline=Frontend Developer React`
48+
- `interestedRoles=Frontend Developer,Fullstack Developer`
49+
50+
Esto permite que el frontend use un fallback contextual si la generacion principal falla.
51+
52+
## Respuesta esperada
53+
54+
La respuesta actual puede mantenerse, pero se recomienda enriquecer el bloque `profile` para trazabilidad:
55+
56+
```ts
57+
interface GeneratedTestsResponseDto {
58+
psychotechnicalTests: GeneratedTest[];
59+
technicalTests: GeneratedTest[];
60+
totalTests: number;
61+
profile: {
62+
fullName: string;
63+
technicalSkillsCount: number;
64+
totalQuestionsCount: number;
65+
professionalArea?: string;
66+
};
67+
}
68+
```
69+
70+
## Criterios de aceptacion
71+
72+
1. Talento con skills `React`, `TypeScript` y area `Frontend Developer` recibe prueba tecnica orientada a frontend.
73+
2. Talento con `Java`, `SQL` y area `Backend Developer` recibe prueba tecnica orientada a backend.
74+
3. Si no hay skills guardadas, backend responde sin `500` y puede caer a preguntas generales.
75+
4. El contrato anterior sin payload sigue siendo valido.
76+
77+
## Nota de compatibilidad frontend
78+
79+
Frontend implementado:
80+
81+
- envia contexto tecnico en `POST /assessments/me/generate-tests`
82+
- intenta fallback legacy con payload vacio si backend rechaza campos nuevos
83+
- usa fallback contextual con `GET /assessments/technical-tests/questions` si la generacion principal falla
84+
85+
## Recomendacion tecnica backend
86+
87+
- Añadir DTO con propiedades opcionales.
88+
- No romper requests legacy con body vacio.
89+
- Mapear skills/area a categorias o tags de preguntas.
90+
- Agregar tests de integracion para contexto frontend y flujo legacy.

Frontend/appTalenFront/src/components/AssessmentTestsPanel.tsx

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,24 @@ import {
1717
consolidateMyAssessment,
1818
generateTestsForProfile,
1919
getPsychotechnicalTestQuestions,
20-
getTechnicalTestQuestions,
20+
getTechnicalTestQuestionsForContext,
2121
getMyAllTestResults,
2222
submitTechnicalTest,
2323
submitPsychotechnicalTest,
2424
} from '../services/assessment.service';
2525
import { generateMyLearningPath } from '../services/learning.service';
26-
import type { GeneratedTestsResponseDto, GeneratedTest, AssessmentTestResultEntity, AssessmentTestQuestion, AssessmentQuestionOption } from '../types/assessment.types';
26+
import { getMyProfile, getMyLatestCvDiagnostic } from '../services/profile.service';
27+
import { getMySkills } from '../services/skill.service';
28+
import type { CvDiagnostic, Profile } from '../types/profile.types';
29+
import type { UserSkill } from '../types/skill.types';
30+
import type {
31+
GeneratedTestsResponseDto,
32+
GeneratedTest,
33+
AssessmentTestResultEntity,
34+
AssessmentTestQuestion,
35+
AssessmentQuestionOption,
36+
GenerateTestsForProfileDto,
37+
} from '../types/assessment.types';
2738

2839
interface AssessmentTestsPanelProps {
2940
activeTab: 'Tecnica' | 'Psicotecnica' | 'Resultados';
@@ -35,6 +46,7 @@ export const AssessmentTestsPanel = ({
3546
onTestCompleted,
3647
}: AssessmentTestsPanelProps) => {
3748
const [generatedTests, setGeneratedTests] = useState<GeneratedTestsResponseDto | null>(null);
49+
const [testsContext, setTestsContext] = useState<GenerateTestsForProfileDto | null>(null);
3850
const [isLoading, setIsLoading] = useState(true);
3951
const [isPreparingTest, setIsPreparingTest] = useState(false);
4052
const [currentTestId, setCurrentTestId] = useState<string | null>(null);
@@ -43,6 +55,76 @@ export const AssessmentTestsPanel = ({
4355
const [submitSuccess, setSubmitSuccess] = useState<string | null>(null);
4456
const [testsErrorMessage, setTestsErrorMessage] = useState<string | null>(null);
4557

58+
const formatInterestedRole = useCallback(
59+
(role: string): string =>
60+
role
61+
.split('_')
62+
.map((segment) => `${segment.slice(0, 1)}${segment.slice(1).toLowerCase()}`)
63+
.join(' '),
64+
[],
65+
);
66+
67+
const extractProfessionalAreaFromDiagnostic = useCallback((
68+
diagnostic: CvDiagnostic | null,
69+
): string | undefined => {
70+
const snapshot = diagnostic?.snapshot as
71+
| {
72+
profile?: { title?: string };
73+
experience?: Array<{ position?: string }>;
74+
}
75+
| undefined;
76+
77+
const profileTitle = snapshot?.profile?.title?.trim();
78+
if (profileTitle) {
79+
return profileTitle;
80+
}
81+
82+
const firstExperienceTitle = snapshot?.experience?.[0]?.position?.trim();
83+
return firstExperienceTitle || undefined;
84+
}, []);
85+
86+
const buildTestsContext = useCallback(async (): Promise<GenerateTestsForProfileDto> => {
87+
if (activeTab !== 'Tecnica') {
88+
return {};
89+
}
90+
91+
const [profile, skills, diagnostic] = await Promise.all([
92+
getMyProfile().catch(() => null as Profile | null),
93+
getMySkills().catch(() => [] as UserSkill[]),
94+
getMyLatestCvDiagnostic().catch(() => null as CvDiagnostic | null),
95+
]);
96+
97+
const technicalSkillsFromProfile = skills
98+
.filter((userSkill) => userSkill.skill?.category?.toUpperCase().includes('TECHNICAL'))
99+
.map((userSkill) => userSkill.skill?.name?.trim() ?? '')
100+
.filter((skillName) => skillName.length > 0);
101+
102+
const technicalSkills = Array.from(
103+
new Set([
104+
...technicalSkillsFromProfile,
105+
...(diagnostic?.technicalSkills ?? []).map((skill) => skill.trim()).filter(Boolean),
106+
]),
107+
).slice(0, 8);
108+
109+
const interestedRoles = Array.isArray(profile?.interestedRoles)
110+
? profile.interestedRoles.map(formatInterestedRole)
111+
: [];
112+
113+
const professionalArea =
114+
profile?.headline?.trim() ||
115+
extractProfessionalAreaFromDiagnostic(diagnostic) ||
116+
interestedRoles[0];
117+
118+
return {
119+
technicalSkills,
120+
professionalArea,
121+
headline: profile?.headline?.trim() || undefined,
122+
interestedRoles,
123+
};
124+
}, [activeTab, extractProfessionalAreaFromDiagnostic, formatInterestedRole]);
125+
126+
const contextualProfessionalArea = testsContext?.professionalArea;
127+
46128
const buildFallbackTest = useCallback((
47129
type: 'TECHNICAL' | 'PSYCHOTECHNICAL',
48130
questions: AssessmentTestQuestion[],
@@ -54,13 +136,15 @@ export const AssessmentTestsPanel = ({
54136
: 'Prueba psicotecnica inicial',
55137
description:
56138
type === 'TECHNICAL'
57-
? 'Evaluacion tecnica general generada a partir de preguntas base del sistema.'
139+
? contextualProfessionalArea
140+
? `Evaluacion tecnica generada para ${contextualProfessionalArea} a partir de preguntas base del sistema.`
141+
: 'Evaluacion tecnica general generada a partir de preguntas base del sistema.'
58142
: 'Evaluacion psicotecnica general generada a partir de preguntas base del sistema.',
59143
type,
60144
questionCount: questions.length,
61145
estimatedDurationMin: Math.max(10, Math.ceil(questions.length * 1.5)),
62146
questions,
63-
}), []);
147+
}), [contextualProfessionalArea]);
64148

65149
const withFallbackTests = useCallback(async (
66150
tests: GeneratedTestsResponseDto,
@@ -72,7 +156,7 @@ export const AssessmentTestsPanel = ({
72156
};
73157

74158
if (activeTab === 'Tecnica' && nextTests.technicalTests.length === 0) {
75-
const technicalQuestions = await getTechnicalTestQuestions().catch(() => []);
159+
const technicalQuestions = await getTechnicalTestQuestionsForContext(testsContext ?? undefined).catch(() => []);
76160
if (technicalQuestions.length > 0) {
77161
nextTests.technicalTests = [buildFallbackTest('TECHNICAL', technicalQuestions)];
78162
}
@@ -91,7 +175,7 @@ export const AssessmentTestsPanel = ({
91175
nextTests.technicalTests.length + nextTests.psychotechnicalTests.length;
92176

93177
return nextTests;
94-
}, [activeTab, buildFallbackTest]);
178+
}, [activeTab, buildFallbackTest, testsContext]);
95179

96180
const getTestsForTab = (testsResponse?: GeneratedTestsResponseDto | null): GeneratedTest[] => {
97181
const source = testsResponse ?? generatedTests;
@@ -104,7 +188,9 @@ export const AssessmentTestsPanel = ({
104188
const loadGeneratedTests = useCallback(async (): Promise<GeneratedTestsResponseDto | null> => {
105189
try {
106190
setTestsErrorMessage(null);
107-
const tests = await generateTestsForProfile();
191+
const nextContext = await buildTestsContext();
192+
setTestsContext(nextContext);
193+
const tests = await generateTestsForProfile(nextContext);
108194
const normalizedTests = await withFallbackTests(tests);
109195
setGeneratedTests(normalizedTests);
110196
return normalizedTests;
@@ -116,7 +202,7 @@ export const AssessmentTestsPanel = ({
116202
setTestsErrorMessage(message);
117203
return null;
118204
}
119-
}, [withFallbackTests]);
205+
}, [buildTestsContext, withFallbackTests]);
120206

121207
useEffect(() => {
122208
const loadData = async () => {
@@ -145,7 +231,7 @@ export const AssessmentTestsPanel = ({
145231
if (availableTests.length === 0) {
146232
setTestsErrorMessage(
147233
activeTab === 'Tecnica'
148-
? 'No se pudo generar una prueba tecnica todavia. Verifica que tengas skills tecnicas guardadas.'
234+
? 'No se pudo generar una prueba tecnica todavia. Verifica que tengas skills tecnicas guardadas y un area profesional definida.'
149235
: 'No se pudo generar una prueba psicotecnica todavia. Intenta nuevamente en unos segundos.',
150236
);
151237
return;
@@ -361,6 +447,7 @@ export const AssessmentTestsPanel = ({
361447
: 'Iniciar prueba psicotecnica';
362448
const generatedTechnicalSkillsCount = generatedTests?.profile.technicalSkillsCount ?? 0;
363449
const generatedTotalTests = generatedTests?.totalTests ?? 0;
450+
const contextualTechnicalSkills = testsContext?.technicalSkills ?? [];
364451

365452
if (tests.length === 0) {
366453
return (
@@ -423,6 +510,24 @@ export const AssessmentTestsPanel = ({
423510
: 'Evaluaciones psicotecnicas para medir aptitudes generales, razonamiento logico y habilidades blandas.'}
424511
</Typography>
425512

513+
{activeTab === 'Tecnica' && (contextualProfessionalArea || contextualTechnicalSkills.length > 0) && (
514+
<Box sx={{ mt: 2, display: 'flex', flexWrap: 'wrap', gap: 1 }}>
515+
{contextualProfessionalArea && (
516+
<Chip
517+
label={`Area profesional: ${contextualProfessionalArea}`}
518+
sx={{ bgcolor: '#E8F0FB', color: '#1D4678', fontWeight: 700 }}
519+
/>
520+
)}
521+
{contextualTechnicalSkills.slice(0, 5).map((skill) => (
522+
<Chip
523+
key={`technical-skill-${skill}`}
524+
label={skill}
525+
sx={{ bgcolor: '#F3E5F5', color: '#6A1B9A', fontWeight: 700 }}
526+
/>
527+
))}
528+
</Box>
529+
)}
530+
426531
<Box sx={{ mt: 2.5, display: 'flex', flexWrap: 'wrap', gap: 1.2 }}>
427532
<Button
428533
variant="contained"
@@ -693,5 +798,3 @@ export const AssessmentResultsPanel = ({
693798
</Paper>
694799
);
695800
};
696-
697-

0 commit comments

Comments
 (0)