Nuevo Commit

This commit is contained in:
2025-11-18 16:46:20 -03:00
parent be30b3ca18
commit 443de4ec0e
12 changed files with 1969 additions and 206 deletions

180
backend/add_questions.py Normal file
View File

@@ -0,0 +1,180 @@
"""
Script para agregar preguntas a un checklist basado en el formato del PDF
Ejecutar: docker cp add_questions.py checklist-backend:/app/ && docker-compose exec -T backend python /app/add_questions.py
"""
from app.core.database import SessionLocal
from app.models import Checklist, Question
# ID del checklist al que quieres agregar preguntas
CHECKLIST_ID = 2 # Cambia este número según el ID de tu checklist
db = SessionLocal()
# Verificar que el checklist existe
checklist = db.query(Checklist).filter(Checklist.id == CHECKLIST_ID).first()
if not checklist:
print(f"❌ Checklist con ID {CHECKLIST_ID} no encontrado")
exit(1)
print(f"✅ Agregando preguntas al checklist: {checklist.name}")
# Definir todas las preguntas por sección
questions_data = [
# DOCUMENTACIÓN
('Documentación', 'Cédula Verde', 'pass_fail', 2, False),
('Documentación', 'Verificación Técnica Vehicular', 'pass_fail', 2, False),
('Documentación', 'Póliza de Seguro', 'pass_fail', 2, False),
('Documentación', 'Licencia de conducir del chofer', 'pass_fail', 2, False),
# EXTERIOR DEL VEHÍCULO
('Exterior', 'Estado de carrocería', 'good_bad', 1, True),
('Exterior', 'Espejo retrovisor izquierdo', 'pass_fail', 1, True),
('Exterior', 'Espejo retrovisor derecho', 'pass_fail', 1, True),
('Exterior', 'Parabrisas delantero', 'pass_fail', 1, True),
('Exterior', 'Luneta trasera', 'pass_fail', 1, True),
('Exterior', 'Cristales laterales', 'pass_fail', 1, True),
('Exterior', 'Escobillas limpiaparabrisas', 'pass_fail', 1, True),
('Exterior', 'Antena', 'pass_fail', 1, False),
('Exterior', 'Tapa de combustible', 'pass_fail', 1, True),
('Exterior', 'Paragolpes delantero', 'pass_fail', 1, True),
('Exterior', 'Paragolpes trasero', 'pass_fail', 1, True),
# NEUMÁTICOS
('Neumáticos', 'Neumático delantero izquierdo - Presión', 'pass_fail', 1, True),
('Neumáticos', 'Neumático delantero izquierdo - Banda de rodadura', 'good_bad', 2, True),
('Neumáticos', 'Neumático delantero derecho - Presión', 'pass_fail', 1, True),
('Neumáticos', 'Neumático delantero derecho - Banda de rodadura', 'good_bad', 2, True),
('Neumáticos', 'Neumático trasero izquierdo - Presión', 'pass_fail', 1, True),
('Neumáticos', 'Neumático trasero izquierdo - Banda de rodadura', 'good_bad', 2, True),
('Neumáticos', 'Neumático trasero derecho - Presión', 'pass_fail', 1, True),
('Neumáticos', 'Neumático trasero derecho - Banda de rodadura', 'good_bad', 2, True),
('Neumáticos', 'Neumático de auxilio - Estado', 'pass_fail', 1, True),
# SISTEMA ELÉCTRICO
('Sistema Eléctrico', 'Batería - Estado', 'good_bad', 2, True),
('Sistema Eléctrico', 'Luces de posición delanteras', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces bajas', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces altas', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces de giro delanteras', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces de freno', 'pass_fail', 2, False),
('Sistema Eléctrico', 'Luces de retroceso', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces traseras', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces de giro traseras', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luz de patente', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luz interior', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Bocina', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Testigos en tablero', 'pass_fail', 1, True),
# MOTOR
('Motor', 'Nivel de aceite', 'pass_fail', 2, True),
('Motor', 'Fugas de aceite', 'pass_fail', 2, True),
('Motor', 'Filtro de aceite', 'status', 1, True),
('Motor', 'Nivel de refrigerante', 'pass_fail', 1, True),
('Motor', 'Fugas de refrigerante', 'pass_fail', 2, True),
('Motor', 'Estado de mangueras', 'pass_fail', 1, True),
('Motor', 'Filtro de aire', 'status', 1, True),
('Motor', 'Correa de distribución', 'pass_fail', 2, True),
('Motor', 'Correas auxiliares', 'pass_fail', 1, True),
# FRENOS
('Frenos', 'Pastillas/zapatas delanteras', 'good_bad', 2, True),
('Frenos', 'Discos/tambores delanteros', 'good_bad', 2, True),
('Frenos', 'Pastillas/zapatas traseras', 'good_bad', 2, True),
('Frenos', 'Discos/tambores traseros', 'good_bad', 2, True),
('Frenos', 'Nivel de líquido de frenos', 'pass_fail', 2, False),
('Frenos', 'Porcentaje de humedad en líquido', 'numeric', 1, False),
('Frenos', 'Freno de mano', 'pass_fail', 2, False),
# SUSPENSIÓN Y DIRECCIÓN
('Suspensión', 'Amortiguadores delanteros', 'pass_fail', 2, True),
('Suspensión', 'Amortiguadores traseros', 'pass_fail', 2, True),
('Suspensión', 'Cojinetes de ruedas', 'pass_fail', 2, False),
('Suspensión', 'Rótulas', 'pass_fail', 2, True),
('Suspensión', 'Bieletas', 'pass_fail', 1, True),
('Suspensión', 'Bujes', 'pass_fail', 1, True),
('Dirección', 'Nivel de líquido de dirección', 'pass_fail', 1, True),
('Dirección', 'Estado de cremallera/caja', 'pass_fail', 2, True),
('Dirección', 'Fugas en dirección', 'pass_fail', 2, True),
# INTERIOR
('Interior', 'Tablero de instrumentos', 'pass_fail', 1, True),
('Interior', 'Funcionamiento de velocímetro', 'pass_fail', 1, False),
('Interior', 'Funcionamiento de cuentakilómetros', 'pass_fail', 1, False),
('Interior', 'Cinturones de seguridad delanteros', 'pass_fail', 2, True),
('Interior', 'Cinturones de seguridad traseros', 'pass_fail', 2, True),
('Interior', 'Asientos', 'pass_fail', 1, True),
('Interior', 'Apoyacabezas', 'pass_fail', 1, True),
('Interior', 'Aire acondicionado', 'pass_fail', 1, False),
('Interior', 'Calefacción', 'pass_fail', 1, False),
('Interior', 'Radio/sistema de audio', 'pass_fail', 1, False),
('Interior', 'Ceniceros', 'pass_fail', 1, False),
('Interior', 'Encendedor', 'pass_fail', 1, False),
('Interior', 'Guantera', 'pass_fail', 1, False),
('Interior', 'Tapizado', 'good_bad', 1, True),
# SEGURIDAD
('Seguridad', 'Matafuego - Fecha de vencimiento', 'pass_fail', 2, True),
('Seguridad', 'Matafuego - Presión', 'pass_fail', 2, True),
('Seguridad', 'Balizas triangulares', 'pass_fail', 1, True),
('Seguridad', 'Botiquín de primeros auxilios', 'pass_fail', 1, True),
('Seguridad', 'Llave de ruedas', 'pass_fail', 1, False),
('Seguridad', 'Gato hidráulico', 'pass_fail', 1, False),
('Seguridad', 'Chaleco reflectivo', 'pass_fail', 1, False),
# TRANSMISIÓN
('Transmisión', 'Nivel de aceite de transmisión', 'pass_fail', 1, True),
('Transmisión', 'Fugas en transmisión', 'pass_fail', 2, True),
('Transmisión', 'Estado de embrague', 'pass_fail', 2, False),
('Transmisión', 'Funcionamiento de cambios', 'pass_fail', 2, False),
# ESCAPE
('Escape', 'Estado del sistema de escape', 'pass_fail', 1, True),
('Escape', 'Fugas en escape', 'pass_fail', 2, True),
('Escape', 'Silenciador', 'pass_fail', 1, True),
('Escape', 'Catalizador', 'pass_fail', 1, True),
# PRUEBA DE RUTA
('Prueba de Ruta', 'Arranque del motor', 'pass_fail', 2, False),
('Prueba de Ruta', 'Ralentí estable', 'pass_fail', 1, False),
('Prueba de Ruta', 'Aceleración', 'pass_fail', 2, False),
('Prueba de Ruta', 'Frenado', 'pass_fail', 2, False),
('Prueba de Ruta', 'Dirección', 'pass_fail', 2, False),
('Prueba de Ruta', 'Cambios de marcha', 'pass_fail', 2, False),
('Prueba de Ruta', 'Ruidos anormales', 'pass_fail', 2, True),
('Prueba de Ruta', 'Vibraciones', 'pass_fail', 1, True),
]
# Agregar todas las preguntas
max_score = 0
for idx, (section, text, qtype, points, photos) in enumerate(questions_data):
question = Question(
checklist_id=checklist.id,
section=section,
text=text,
type=qtype,
points=points,
order=idx + 1,
allow_photos=photos,
max_photos=3 if photos else 0,
requires_comment_on_fail=False
)
db.add(question)
max_score += points
# Actualizar puntuación máxima del checklist
checklist.max_score = max_score
db.commit()
print(f'✅ Se agregaron {len(questions_data)} preguntas al checklist')
print(f'✅ Puntuación máxima: {max_score} puntos')
print(f'\nDesglose por sección:')
# Contar preguntas por sección
from collections import Counter
sections = Counter([s for s, _, _, _, _ in questions_data])
for section, count in sections.items():
section_points = sum(p for s, _, _, p, _ in questions_data if s == section)
print(f' - {section}: {count} preguntas ({section_points} puntos)')
db.close()

View File

@@ -24,7 +24,12 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
def decode_access_token(token: str):
try:
print(f"Attempting to decode token: {token[:50]}...") # Debug
print(f"Using SECRET_KEY: {settings.SECRET_KEY[:20]}...") # Debug
print(f"Using ALGORITHM: {settings.ALGORITHM}") # Debug
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
print(f"Successfully decoded payload: {payload}") # Debug
return payload
except JWTError:
except JWTError as e:
print(f"JWT decode error: {e}") # Debug
return None

View File

@@ -34,14 +34,18 @@ def get_current_user(
):
token = credentials.credentials
payload = decode_access_token(token)
print(f"Token payload: {payload}") # Debug
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido o expirado"
)
user = db.query(models.User).filter(models.User.id == payload.get("sub")).first()
user_id = int(payload.get("sub"))
print(f"Looking for user ID: {user_id}") # Debug
user = db.query(models.User).filter(models.User.id == user_id).first()
if user is None:
print(f"User not found with ID: {user_id}") # Debug
raise HTTPException(status_code=404, detail="Usuario no encontrado")
return user
@@ -79,7 +83,7 @@ def login(user_login: schemas.UserLogin, db: Session = Depends(get_db)):
detail="Usuario o contraseña incorrectos"
)
access_token = create_access_token(data={"sub": user.id, "role": user.role})
access_token = create_access_token(data={"sub": str(user.id), "role": user.role})
return {
"access_token": access_token,
"token_type": "bearer",
@@ -254,7 +258,7 @@ def get_inspection(
current_user: models.User = Depends(get_current_user)
):
inspection = db.query(models.Inspection).options(
joinedload(models.Inspection.checklist),
joinedload(models.Inspection.checklist).joinedload(models.Checklist.questions),
joinedload(models.Inspection.mechanic),
joinedload(models.Inspection.answers).joinedload(models.Answer.question),
joinedload(models.Inspection.answers).joinedload(models.Answer.media_files)

View File

@@ -167,11 +167,11 @@ class MediaFile(MediaFileBase):
class ChecklistWithQuestions(Checklist):
questions: List[Question] = []
class InspectionDetail(Inspection):
checklist: Checklist
mechanic: User
answers: List[Answer] = []
class AnswerWithMedia(Answer):
media_files: List[MediaFile] = []
question: Question
class InspectionDetail(Inspection):
checklist: ChecklistWithQuestions
mechanic: User
answers: List[AnswerWithMedia] = []

View File

@@ -0,0 +1,72 @@
from app.core.database import SessionLocal
from app.models import Checklist, Question
db = SessionLocal()
# Crear checklist
checklist = Checklist(
name='Mantenimiento Preventivo',
description='Checklist estándar de mantenimiento preventivo',
ai_mode='assisted',
scoring_enabled=True,
is_active=True,
created_by=1
)
db.add(checklist)
db.commit()
db.refresh(checklist)
# Crear preguntas por sección
questions_data = [
# Sistema Eléctrico
('Sistema Eléctrico', 'Estado de la batería', 'good_bad', 1, True),
('Sistema Eléctrico', 'Bocina', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Luces (posición, cruce, carretera)', 'pass_fail', 1, False),
('Sistema Eléctrico', 'Testigos en cuadro', 'pass_fail', 1, True),
# Frenos
('Frenos', 'Frenos (pastillas, discos)', 'pass_fail', 2, True),
('Frenos', 'Líquido de freno', 'pass_fail', 1, False),
('Frenos', 'Porcentaje de humedad', 'numeric', 1, False),
# Motor
('Motor', 'Nivel de aceite', 'pass_fail', 1, True),
('Motor', 'Fugas de aceite', 'pass_fail', 2, True),
('Motor', 'Filtro de aceite', 'status', 1, True),
('Motor', 'Fugas de refrigerante', 'pass_fail', 2, True),
# Neumáticos
('Neumáticos', 'Presión neumáticos', 'pass_fail', 1, False),
('Neumáticos', 'Banda de rodadura', 'good_bad', 1, True),
# Suspensión
('Suspensión', 'Amortiguadores', 'pass_fail', 2, True),
('Suspensión', 'Cojinetes de ruedas', 'pass_fail', 1, False),
# Varios
('Exterior', 'Estado carrocería', 'good_bad', 1, True),
('Exterior', 'Escobillas limpiaparabrisas', 'pass_fail', 1, True),
('Interior', 'Aire acondicionado', 'pass_fail', 1, False),
('Pruebas', 'Prueba dinámica del vehículo', 'pass_fail', 2, False),
]
max_score = 0
for idx, (section, text, qtype, points, photos) in enumerate(questions_data):
question = Question(
checklist_id=checklist.id,
section=section,
text=text,
type=qtype,
points=points,
order=idx + 1,
allow_photos=photos,
max_photos=3
)
db.add(question)
max_score += points
checklist.max_score = max_score
db.commit()
print(f'✅ Checklist creado con {len(questions_data)} preguntas')
print(f'✅ Puntuación máxima: {max_score}')

View File

@@ -5,8 +5,10 @@ alembic==1.13.1
psycopg2-binary==2.9.9
pydantic==2.5.3
pydantic-settings==2.1.0
email-validator==2.1.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
passlib==1.7.4
bcrypt==4.0.1
python-multipart==0.0.6
openai==1.10.0
Pillow==10.2.0