adriax912 commited on
Commit
e65022a
·
1 Parent(s): 61a706f

Corregir estructura frontend y metadata para Space

Browse files
README.md CHANGED
@@ -1,3 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Modelo de Detección Temprana de Riesgo Psicológico en Estudiantes
2
 
3
  Este proyecto es una aplicación web que analiza textos académicos en español y detecta señales tempranas de riesgo psicológico (ansiedad/depresión) usando FastAPI y la API de Groq.
 
1
+ ---
2
+ language: es
3
+ license: mit
4
+ tags:
5
+ - salud-mental
6
+ - psicologia
7
+ - deteccion-de-riesgo
8
+ - education
9
+ - fastapi
10
+ - groq
11
+ - docker
12
+ ---
13
+
14
  # Modelo de Detección Temprana de Riesgo Psicológico en Estudiantes
15
 
16
  Este proyecto es una aplicación web que analiza textos académicos en español y detecta señales tempranas de riesgo psicológico (ansiedad/depresión) usando FastAPI y la API de Groq.
frontend/{style.css → css/style.css} RENAMED
File without changes
frontend/js/app.js ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const form = document.getElementById('analisisForm');
3
+ const texto = document.getElementById('texto');
4
+ const fileInput = document.getElementById('fileInput');
5
+ const dropZone = document.getElementById('dropZone');
6
+ const loading = document.getElementById('loading');
7
+ const resultado = document.getElementById('resultado');
8
+ const nivelEl = document.getElementById('nivel');
9
+ const indicadoresEl = document.getElementById('indicadores');
10
+ const analisisEl = document.getElementById('analisis');
11
+ const recomendacionesEl = document.getElementById('recomendaciones');
12
+ const submitBtn = document.getElementById('submitBtn');
13
+ const fileStatus = document.getElementById('fileStatus');
14
+ const fileName = document.getElementById('fileName');
15
+ const fileSize = document.getElementById('fileSize');
16
+ const clearFileBtn = document.getElementById('clearFileBtn');
17
+ const uploadProgress = document.getElementById('uploadProgress');
18
+ const progressFill = document.getElementById('progressFill');
19
+ const progressText = document.getElementById('progressText');
20
+
21
+ // Validar tipo de archivo
22
+ const validateFile = (file) => {
23
+ const validTypes = ['text/plain', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
24
+ const validExtensions = ['.txt', '.docx'];
25
+ const fileName = file.name.toLowerCase();
26
+ const hasValidExt = validExtensions.some(ext => fileName.endsWith(ext));
27
+ const hasValidType = validTypes.includes(file.type) || fileName.endsWith('.docx');
28
+ const maxSize = 5 * 1024 * 1024; // 5MB
29
+
30
+ if (!hasValidExt) return { valid: false, error: 'Formato no válido. Solo .txt y .docx' };
31
+ if (file.size > maxSize) return { valid: false, error: 'Archivo muy grande (máx. 5MB)' };
32
+ return { valid: true };
33
+ };
34
+
35
+ // Mostrar archivo seleccionado
36
+ const showFileStatus = (file) => {
37
+ const validation = validateFile(file);
38
+ if (!validation.valid) {
39
+ alert('❌ ' + validation.error);
40
+ fileInput.value = '';
41
+ fileStatus.classList.add('hidden');
42
+ return false;
43
+ }
44
+
45
+ fileName.textContent = file.name;
46
+ fileSize.textContent = (file.size / 1024).toFixed(1) + ' KB';
47
+ fileStatus.classList.remove('hidden');
48
+ return true;
49
+ };
50
+
51
+ // Limpiar archivo
52
+ clearFileBtn.addEventListener('click', () => {
53
+ fileInput.value = '';
54
+ fileStatus.classList.add('hidden');
55
+ texto.focus();
56
+ });
57
+
58
+ // Eventos de archivo
59
+ fileInput.addEventListener('change', (e) => {
60
+ if (e.target.files.length > 0) {
61
+ showFileStatus(e.target.files[0]);
62
+ }
63
+ });
64
+
65
+ // Drag and drop
66
+ ;['dragenter','dragover'].forEach(evt => {
67
+ dropZone.addEventListener(evt, (e) => { e.preventDefault(); dropZone.classList.add('drag'); });
68
+ })
69
+ ;['dragleave','drop'].forEach(evt => {
70
+ dropZone.addEventListener(evt, (e) => { e.preventDefault(); dropZone.classList.remove('drag'); });
71
+ })
72
+
73
+ dropZone.addEventListener('drop', (e) => {
74
+ const f = e.dataTransfer.files[0];
75
+ if (f) {
76
+ fileInput.files = e.dataTransfer.files;
77
+ showFileStatus(f);
78
+ }
79
+ });
80
+
81
+ // Click on drop zone opens file selector
82
+ dropZone.addEventListener('click', () => fileInput.click());
83
+
84
+ form.addEventListener('submit', async (e) => {
85
+ e.preventDefault();
86
+
87
+ // Validación: texto o archivo
88
+ if (!texto.value.trim() && fileInput.files.length === 0) {
89
+ alert('Por favor ingrese texto o suba un archivo (.txt o .docx).');
90
+ return;
91
+ }
92
+
93
+ // Preparar FormData
94
+ const fd = new FormData();
95
+ if (texto.value.trim()) fd.append('texto', texto.value.trim());
96
+ if (fileInput.files.length > 0) fd.append('archivo', fileInput.files[0]);
97
+
98
+ // UI: mostrar carga
99
+ loading.classList.remove('hidden');
100
+ submitBtn.disabled = true;
101
+ resultado.classList.add('hidden');
102
+
103
+ try {
104
+ // Simular progreso
105
+ uploadProgress.classList.remove('hidden');
106
+ let progress = 0;
107
+ const progressInterval = setInterval(() => {
108
+ progress += Math.random() * 30;
109
+ if (progress > 90) progress = 90;
110
+ progressFill.style.width = progress + '%';
111
+ progressText.textContent = Math.floor(progress) + '%';
112
+ }, 200);
113
+
114
+ const res = await fetch('/api/analizar', {
115
+ method: 'POST',
116
+ body: fd
117
+ });
118
+
119
+ clearInterval(progressInterval);
120
+ progressFill.style.width = '100%';
121
+ progressText.textContent = '100%';
122
+
123
+ if (!res.ok) {
124
+ const err = await res.json().catch(()=>({detail:res.statusText}));
125
+ throw new Error(err.detail || 'Error en la petición');
126
+ }
127
+
128
+ const data = await res.json();
129
+
130
+ // Éxito
131
+ setTimeout(() => {
132
+ uploadProgress.classList.add('hidden');
133
+ progressFill.style.width = '0%';
134
+ progressText.textContent = '0%';
135
+ renderResultado(data);
136
+ }, 300);
137
+
138
+ } catch (err) {
139
+ uploadProgress.classList.add('hidden');
140
+ progressFill.style.width = '0%';
141
+ progressText.textContent = '0%';
142
+ alert('❌ Error: ' + (err.message || err));
143
+ } finally {
144
+ loading.classList.add('hidden');
145
+ submitBtn.disabled = false;
146
+ }
147
+ });
148
+
149
+ function renderResultado(data) {
150
+ // Nivel de riesgo
151
+ const nivel = (data.nivel_riesgo || '').toLowerCase();
152
+ nivelEl.textContent = `Nivel de riesgo: ${data.nivel_riesgo || 'N/A'}`;
153
+ nivelEl.className = 'nivel';
154
+ if (nivel === 'bajo') nivelEl.classList.add('bajo');
155
+ else if (nivel === 'medio') nivelEl.classList.add('medio');
156
+ else if (nivel === 'alto') nivelEl.classList.add('alto');
157
+
158
+ // Indicadores
159
+ indicadoresEl.innerHTML = '';
160
+ if (Array.isArray(data.indicadores_detectados)) {
161
+ data.indicadores_detectados.forEach(it => {
162
+ const li = document.createElement('li'); li.textContent = it; indicadoresEl.appendChild(li);
163
+ });
164
+ }
165
+
166
+ // Analisis y recomendaciones
167
+ analisisEl.textContent = data.analisis_linguistico || '';
168
+ recomendacionesEl.textContent = data.recomendaciones_psicologicas || '';
169
+
170
+ resultado.classList.remove('hidden');
171
+ // Scroll to results
172
+ resultado.scrollIntoView({behavior:'smooth'});
173
+ }
174
+ });