feat: SAM integration, deployment scripts, and audio response enhancements
- Add SAM (Sistema de Administración Académica) integration with sync endpoints - Add deployment automation (deploy.sh, remote-setup.sh, setup-nginx-ssl.sh) - Add nginx proxy configuration for SSL with Let's Encrypt - Add audio response support for student lessons (migrations, handlers) - Add audio evaluations admin page - Update CORS to support wildcard subdomains for norteamericano.cl - Add comprehensive deployment documentation (DESPLIEGUE.md, ManualDeConfiguracion.md) - Update docker-compose.yml with nginx-proxy and acme-companion services - Remove outdated documentation files Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
-- Migration: Add audio responses table for speaking practice
|
||||
-- Allows students to submit audio recordings and teachers to evaluate them
|
||||
|
||||
-- Table to store student audio responses
|
||||
CREATE TABLE IF NOT EXISTS audio_responses (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
organization_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
course_id UUID NOT NULL,
|
||||
lesson_id UUID NOT NULL,
|
||||
block_id UUID NOT NULL, -- ID del bloque audio-response dentro de la lección
|
||||
prompt TEXT NOT NULL, -- La pregunta/instrucción original
|
||||
transcript TEXT, -- Transcripción del audio (generada por Whisper)
|
||||
audio_url TEXT, -- URL o path del archivo de audio almacenado
|
||||
audio_data BYTEA, -- Audio almacenado en base64 (opcional, para archivos pequeños)
|
||||
|
||||
-- Evaluación de IA
|
||||
ai_score INTEGER, -- Puntaje de IA (0-100)
|
||||
ai_found_keywords TEXT[], -- Keywords encontradas por la IA
|
||||
ai_feedback TEXT, -- Feedback de la IA
|
||||
ai_evaluated_at TIMESTAMPTZ, -- Cuándo fue evaluado por IA
|
||||
|
||||
-- Evaluación del profesor
|
||||
teacher_score INTEGER, -- Puntaje del profesor (0-100)
|
||||
teacher_feedback TEXT, -- Feedback del profesor
|
||||
teacher_evaluated_at TIMESTAMPTZ, -- Cuándo fue evaluado por el profesor
|
||||
teacher_evaluated_by UUID, -- ID del profesor que evaluó
|
||||
|
||||
-- Estado y metadatos
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending', -- 'pending', 'ai_evaluated', 'teacher_evaluated', 'both_evaluated'
|
||||
attempt_number INTEGER NOT NULL DEFAULT 1, -- Número de intento (permite reintentos)
|
||||
duration_seconds INTEGER, -- Duración de la grabación en segundos
|
||||
metadata JSONB, -- Metadatos adicionales (calidad de audio, etc.)
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Foreign keys
|
||||
CONSTRAINT fk_audio_response_organization FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_audio_response_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_audio_response_course FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_audio_response_lesson FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_audio_response_teacher FOREIGN KEY (teacher_evaluated_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT chk_ai_score CHECK (ai_score IS NULL OR (ai_score >= 0 AND ai_score <= 100)),
|
||||
CONSTRAINT chk_teacher_score CHECK (teacher_score IS NULL OR (teacher_score >= 0 AND teacher_score <= 100)),
|
||||
CONSTRAINT chk_attempt_number CHECK (attempt_number >= 1)
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX idx_audio_responses_user_id ON audio_responses(user_id);
|
||||
CREATE INDEX idx_audio_responses_course_id ON audio_responses(course_id);
|
||||
CREATE INDEX idx_audio_responses_lesson_id ON audio_responses(lesson_id);
|
||||
CREATE INDEX idx_audio_responses_block_id ON audio_responses(block_id);
|
||||
CREATE INDEX idx_audio_responses_organization_id ON audio_responses(organization_id);
|
||||
CREATE INDEX idx_audio_responses_status ON audio_responses(status);
|
||||
CREATE INDEX idx_audio_responses_created_at ON audio_responses(created_at DESC);
|
||||
CREATE INDEX idx_audio_responses_teacher_evaluated ON audio_responses(teacher_evaluated_at) WHERE teacher_evaluated_at IS NOT NULL;
|
||||
|
||||
-- Composite index for common queries
|
||||
CREATE INDEX idx_audio_responses_user_lesson_block ON audio_responses(user_id, lesson_id, block_id);
|
||||
|
||||
-- Add comment
|
||||
COMMENT ON TABLE audio_responses IS 'Almacena las respuestas de audio de los estudiantes para ejercicios de speaking practice';
|
||||
COMMENT ON COLUMN audio_responses.status IS 'pending: sin evaluar, ai_evaluated: solo IA, teacher_evaluated: solo profesor, both_evaluated: ambos';
|
||||
|
||||
-- Add updated_at trigger
|
||||
CREATE OR REPLACE FUNCTION update_audio_responses_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_audio_responses_updated_at
|
||||
BEFORE UPDATE ON audio_responses
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_audio_responses_updated_at();
|
||||
|
||||
-- View para profesores: respuestas pendientes de evaluación
|
||||
CREATE VIEW v_pending_audio_responses AS
|
||||
SELECT
|
||||
ar.id,
|
||||
ar.user_id,
|
||||
u.full_name AS student_name,
|
||||
u.email AS student_email,
|
||||
ar.course_id,
|
||||
c.title AS course_title,
|
||||
ar.lesson_id,
|
||||
l.title AS lesson_title,
|
||||
ar.block_id,
|
||||
ar.prompt,
|
||||
ar.transcript,
|
||||
ar.ai_score,
|
||||
ar.ai_feedback,
|
||||
ar.status,
|
||||
ar.created_at,
|
||||
ar.attempt_number
|
||||
FROM audio_responses ar
|
||||
JOIN users u ON ar.user_id = u.id
|
||||
JOIN courses c ON ar.course_id = c.id
|
||||
JOIN lessons l ON ar.lesson_id = l.id
|
||||
WHERE ar.teacher_evaluated_at IS NULL
|
||||
ORDER BY ar.created_at DESC;
|
||||
|
||||
-- View para estadísticas de evaluación
|
||||
CREATE VIEW v_audio_response_stats AS
|
||||
SELECT
|
||||
organization_id,
|
||||
course_id,
|
||||
lesson_id,
|
||||
COUNT(*) AS total_responses,
|
||||
COUNT(*) FILTER (WHERE ai_score IS NOT NULL) AS ai_evaluated,
|
||||
COUNT(*) FILTER (WHERE teacher_score IS NOT NULL) AS teacher_evaluated,
|
||||
COUNT(*) FILTER (WHERE status = 'both_evaluated') AS fully_evaluated,
|
||||
COUNT(*) FILTER (WHERE status = 'pending') AS pending,
|
||||
AVG(ai_score) FILTER (WHERE ai_score IS NOT NULL) AS avg_ai_score,
|
||||
AVG(teacher_score) FILTER (WHERE teacher_score IS NOT NULL) AS avg_teacher_score
|
||||
FROM audio_responses
|
||||
GROUP BY organization_id, course_id, lesson_id;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Migration: Add audio_response_status ENUM type
|
||||
-- This migration creates the ENUM type for audio response status
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE audio_response_status AS ENUM ('pending', 'ai_evaluated', 'teacher_evaluated', 'both_evaluated');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user