first commit
This commit is contained in:
24
backend/.dockerignore
Normal file
24
backend/.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Backend .dockerignore
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
.venv/
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
*.log
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
Dockerfile.prod
|
||||
README.md
|
||||
.env
|
||||
.env.*
|
||||
*.md
|
||||
27
backend/Dockerfile.prod
Normal file
27
backend/Dockerfile.prod
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Instalar dependencias del sistema
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
postgresql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copiar requirements
|
||||
COPY requirements.txt .
|
||||
|
||||
# Instalar dependencias de Python
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copiar código
|
||||
COPY . .
|
||||
|
||||
# Crear directorio para uploads
|
||||
RUN mkdir -p /app/uploads
|
||||
|
||||
# Exponer puerto
|
||||
EXPOSE 8000
|
||||
|
||||
# Comando de producción (múltiples workers)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
|
||||
@@ -1,22 +1,39 @@
|
||||
"""
|
||||
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
|
||||
Ejecutar: 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
|
||||
from app.models import Checklist, Question, User
|
||||
|
||||
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")
|
||||
# Obtener el usuario admin
|
||||
admin = db.query(User).filter(User.username == "admin").first()
|
||||
if not admin:
|
||||
print("❌ Usuario admin no encontrado")
|
||||
exit(1)
|
||||
|
||||
# Verificar si ya existe un checklist, si no, crearlo
|
||||
checklist = db.query(Checklist).filter(Checklist.name == "Inspección Preventiva de Vehículos").first()
|
||||
if not checklist:
|
||||
print("📋 Creando checklist...")
|
||||
checklist = Checklist(
|
||||
name="Inspección Preventiva de Vehículos",
|
||||
description="Checklist completo para inspección preventiva de vehículos",
|
||||
ai_mode="off",
|
||||
scoring_enabled=True,
|
||||
created_by=admin.id
|
||||
)
|
||||
db.add(checklist)
|
||||
db.commit()
|
||||
db.refresh(checklist)
|
||||
print(f"✅ Checklist creado con ID: {checklist.id}")
|
||||
else:
|
||||
print(f"✅ Usando checklist existente: {checklist.name} (ID: {checklist.id})")
|
||||
|
||||
CHECKLIST_ID = checklist.id
|
||||
|
||||
print(f"✅ Agregando preguntas al checklist: {checklist.name}")
|
||||
|
||||
# Definir todas las preguntas por sección
|
||||
|
||||
@@ -9,7 +9,6 @@ from datetime import datetime, timedelta
|
||||
|
||||
from app.core.database import engine, get_db, Base
|
||||
from app.core.security import verify_password, get_password_hash, create_access_token, decode_access_token
|
||||
from app.core.config import settings
|
||||
from app import models, schemas
|
||||
|
||||
# Crear tablas
|
||||
@@ -17,18 +16,15 @@ Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(title="Checklist Inteligente API", version="1.0.0")
|
||||
|
||||
# CORS - Usar configuración de settings
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
allow_origins=["http://localhost:5173", "http://localhost:3000"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Log para debug
|
||||
print(f"🌐 CORS configured for origins: {settings.cors_origins}")
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
# Dependency para obtener usuario actual
|
||||
@@ -451,6 +447,428 @@ async def upload_photo(
|
||||
return media_file
|
||||
|
||||
|
||||
# ============= AI ANALYSIS =============
|
||||
@app.get("/api/ai/models", response_model=List[schemas.AIModelInfo])
|
||||
def get_available_ai_models(current_user: models.User = Depends(get_current_user)):
|
||||
"""Obtener lista de modelos de IA disponibles"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden ver modelos de IA")
|
||||
|
||||
models_list = [
|
||||
# OpenAI Models
|
||||
{
|
||||
"id": "gpt-4o",
|
||||
"name": "GPT-4o (Recomendado)",
|
||||
"provider": "openai",
|
||||
"description": "Modelo multimodal más avanzado de OpenAI, rápido y preciso para análisis de imágenes"
|
||||
},
|
||||
{
|
||||
"id": "gpt-4o-mini",
|
||||
"name": "GPT-4o Mini",
|
||||
"provider": "openai",
|
||||
"description": "Versión compacta y económica de GPT-4o, ideal para análisis rápidos"
|
||||
},
|
||||
{
|
||||
"id": "gpt-4-turbo",
|
||||
"name": "GPT-4 Turbo",
|
||||
"provider": "openai",
|
||||
"description": "Modelo potente con capacidades de visión y contexto amplio"
|
||||
},
|
||||
{
|
||||
"id": "gpt-4-vision-preview",
|
||||
"name": "GPT-4 Vision (Preview)",
|
||||
"provider": "openai",
|
||||
"description": "Modelo específico para análisis de imágenes (versión previa)"
|
||||
},
|
||||
# Gemini Models - Actualizados a versiones 2.0, 2.5 y 3.0
|
||||
{
|
||||
"id": "gemini-3-pro-preview",
|
||||
"name": "Gemini 3 Pro Preview (Último)",
|
||||
"provider": "gemini",
|
||||
"description": "Modelo de próxima generación en preview, máxima capacidad de análisis"
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.5-pro",
|
||||
"name": "Gemini 2.5 Pro (Recomendado)",
|
||||
"provider": "gemini",
|
||||
"description": "Último modelo estable con excelente análisis visual y razonamiento avanzado"
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.5-flash",
|
||||
"name": "Gemini 2.5 Flash",
|
||||
"provider": "gemini",
|
||||
"description": "Versión rápida del 2.5, ideal para inspecciones en tiempo real"
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.0-flash",
|
||||
"name": "Gemini 2.0 Flash",
|
||||
"provider": "gemini",
|
||||
"description": "Modelo rápido y eficiente de la generación 2.0"
|
||||
},
|
||||
{
|
||||
"id": "gemini-1.5-pro-latest",
|
||||
"name": "Gemini 1.5 Pro Latest",
|
||||
"provider": "gemini",
|
||||
"description": "Versión estable 1.5 con contexto de 2M tokens"
|
||||
},
|
||||
{
|
||||
"id": "gemini-1.5-flash-latest",
|
||||
"name": "Gemini 1.5 Flash Latest",
|
||||
"provider": "gemini",
|
||||
"description": "Modelo 1.5 rápido para análisis básicos"
|
||||
}
|
||||
]
|
||||
|
||||
return models_list
|
||||
|
||||
|
||||
@app.get("/api/ai/configuration", response_model=schemas.AIConfiguration)
|
||||
def get_ai_configuration(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: models.User = Depends(get_current_user)
|
||||
):
|
||||
"""Obtener configuración de IA actual"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden ver configuración de IA")
|
||||
|
||||
config = db.query(models.AIConfiguration).filter(
|
||||
models.AIConfiguration.is_active == True
|
||||
).first()
|
||||
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="No hay configuración de IA activa")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@app.post("/api/ai/configuration", response_model=schemas.AIConfiguration)
|
||||
def create_ai_configuration(
|
||||
config: schemas.AIConfigurationCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: models.User = Depends(get_current_user)
|
||||
):
|
||||
"""Crear o actualizar configuración de IA"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden configurar IA")
|
||||
|
||||
# Desactivar configuraciones anteriores
|
||||
db.query(models.AIConfiguration).update({"is_active": False})
|
||||
|
||||
# Crear nueva configuración
|
||||
new_config = models.AIConfiguration(
|
||||
provider=config.provider,
|
||||
api_key=config.api_key,
|
||||
model_name=config.model_name,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
db.add(new_config)
|
||||
db.commit()
|
||||
db.refresh(new_config)
|
||||
|
||||
return new_config
|
||||
|
||||
|
||||
@app.put("/api/ai/configuration/{config_id}", response_model=schemas.AIConfiguration)
|
||||
def update_ai_configuration(
|
||||
config_id: int,
|
||||
config_update: schemas.AIConfigurationUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: models.User = Depends(get_current_user)
|
||||
):
|
||||
"""Actualizar configuración de IA existente"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden actualizar configuración de IA")
|
||||
|
||||
config = db.query(models.AIConfiguration).filter(
|
||||
models.AIConfiguration.id == config_id
|
||||
).first()
|
||||
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Configuración no encontrada")
|
||||
|
||||
# Actualizar campos
|
||||
for key, value in config_update.dict(exclude_unset=True).items():
|
||||
setattr(config, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@app.delete("/api/ai/configuration/{config_id}")
|
||||
def delete_ai_configuration(
|
||||
config_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: models.User = Depends(get_current_user)
|
||||
):
|
||||
"""Eliminar configuración de IA"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="Solo administradores pueden eliminar configuración de IA")
|
||||
|
||||
config = db.query(models.AIConfiguration).filter(
|
||||
models.AIConfiguration.id == config_id
|
||||
).first()
|
||||
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="Configuración no encontrada")
|
||||
|
||||
db.delete(config)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Configuración eliminada correctamente"}
|
||||
|
||||
|
||||
@app.post("/api/analyze-image")
|
||||
async def analyze_image(
|
||||
file: UploadFile = File(...),
|
||||
question_id: int = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: models.User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Analiza una imagen usando IA para sugerir respuestas
|
||||
Usa la configuración de IA activa (OpenAI o Gemini)
|
||||
"""
|
||||
# Obtener configuración de IA activa
|
||||
ai_config = db.query(models.AIConfiguration).filter(
|
||||
models.AIConfiguration.is_active == True
|
||||
).first()
|
||||
|
||||
if not ai_config:
|
||||
return {
|
||||
"status": "disabled",
|
||||
"message": "No hay configuración de IA activa. Configure en Settings."
|
||||
}
|
||||
|
||||
# Guardar imagen temporalmente
|
||||
import base64
|
||||
|
||||
contents = await file.read()
|
||||
image_b64 = base64.b64encode(contents).decode('utf-8')
|
||||
|
||||
# Obtener contexto de la pregunta si se proporciona
|
||||
question_obj = None
|
||||
if question_id:
|
||||
question_obj = db.query(models.Question).filter(models.Question.id == question_id).first()
|
||||
|
||||
try:
|
||||
# Construir prompt dinámico basado en la pregunta específica
|
||||
if question_obj:
|
||||
# Prompt altamente específico para la pregunta
|
||||
question_text = question_obj.text
|
||||
question_type = question_obj.type
|
||||
section = question_obj.section
|
||||
|
||||
system_prompt = f"""Eres un mecánico experto realizando una inspección vehicular.
|
||||
|
||||
PREGUNTA ESPECÍFICA A RESPONDER: "{question_text}"
|
||||
Sección: {section}
|
||||
|
||||
Analiza la imagen ÚNICAMENTE para responder esta pregunta específica.
|
||||
Sé directo y enfócate solo en lo que la pregunta solicita.
|
||||
|
||||
Responde en formato JSON:
|
||||
{{
|
||||
"status": "ok|minor|critical",
|
||||
"observations": "Respuesta específica a: {question_text}",
|
||||
"recommendation": "Acción si aplica",
|
||||
"confidence": 0.0-1.0
|
||||
}}
|
||||
|
||||
IMPORTANTE:
|
||||
- Responde SOLO lo que la pregunta pide
|
||||
- No des información genérica del vehículo
|
||||
- Sé específico y técnico
|
||||
- Si la pregunta es pass/fail, indica claramente si pasa o falla
|
||||
- Si la pregunta es bueno/regular/malo, indica el estado específico del componente"""
|
||||
|
||||
user_message = f"Inspecciona la imagen y responde específicamente: {question_text}"
|
||||
else:
|
||||
# Fallback para análisis general
|
||||
system_prompt = """Eres un experto mecánico automotriz. Analiza la imagen y proporciona:
|
||||
1. Estado del componente (bueno/regular/malo)
|
||||
2. Nivel de criticidad (ok/minor/critical)
|
||||
3. Observaciones técnicas breves
|
||||
4. Recomendación de acción
|
||||
|
||||
Responde en formato JSON:
|
||||
{
|
||||
"status": "ok|minor|critical",
|
||||
"observations": "descripción técnica",
|
||||
"recommendation": "acción sugerida",
|
||||
"confidence": 0.0-1.0
|
||||
}"""
|
||||
user_message = "Analiza este componente del vehículo para la inspección general."
|
||||
|
||||
if ai_config.provider == "openai":
|
||||
import openai
|
||||
openai.api_key = ai_config.api_key
|
||||
|
||||
response = openai.ChatCompletion.create(
|
||||
model=ai_config.model_name,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": user_message
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
max_tokens=500
|
||||
)
|
||||
|
||||
ai_response = response.choices[0].message.content
|
||||
|
||||
elif ai_config.provider == "gemini":
|
||||
import google.generativeai as genai
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
genai.configure(api_key=ai_config.api_key)
|
||||
model = genai.GenerativeModel(ai_config.model_name)
|
||||
|
||||
# Convertir base64 a imagen PIL
|
||||
image = Image.open(BytesIO(contents))
|
||||
|
||||
prompt = f"{system_prompt}\n\n{user_message}"
|
||||
response = model.generate_content([prompt, image])
|
||||
ai_response = response.text
|
||||
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Provider {ai_config.provider} no soportado"
|
||||
}
|
||||
|
||||
# Intentar parsear como JSON, si falla, usar texto plano
|
||||
try:
|
||||
import json
|
||||
import re
|
||||
|
||||
# Limpiar markdown code blocks si existen
|
||||
cleaned_response = ai_response.strip()
|
||||
|
||||
# Remover ```json ... ``` si existe
|
||||
if cleaned_response.startswith('```'):
|
||||
# Extraer contenido entre ``` markers
|
||||
match = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', cleaned_response, re.DOTALL)
|
||||
if match:
|
||||
cleaned_response = match.group(1).strip()
|
||||
|
||||
analysis = json.loads(cleaned_response)
|
||||
except:
|
||||
# Si no es JSON válido, crear estructura básica
|
||||
analysis = {
|
||||
"status": "ok",
|
||||
"observations": ai_response,
|
||||
"recommendation": "Revisar manualmente",
|
||||
"confidence": 0.7
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"analysis": analysis,
|
||||
"raw_response": ai_response,
|
||||
"model": ai_config.model_name,
|
||||
"provider": ai_config.provider
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error en análisis AI: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": "Error analyzing image with AI. Please check AI configuration in Settings."
|
||||
}
|
||||
|
||||
try:
|
||||
import openai
|
||||
openai.api_key = settings.OPENAI_API_KEY
|
||||
|
||||
# Prompt especializado para inspección vehicular
|
||||
system_prompt = """Eres un experto mecánico automotriz. Analiza la imagen y proporciona:
|
||||
1. Estado del componente (bueno/regular/malo)
|
||||
2. Nivel de criticidad (ok/minor/critical)
|
||||
3. Observaciones técnicas breves
|
||||
4. Recomendación de acción
|
||||
|
||||
Responde en formato JSON:
|
||||
{
|
||||
"status": "ok|minor|critical",
|
||||
"observations": "descripción técnica",
|
||||
"recommendation": "acción sugerida",
|
||||
"confidence": 0.0-1.0
|
||||
}"""
|
||||
|
||||
response = openai.ChatCompletion.create(
|
||||
model="gpt-4-vision-preview" if "gpt-4" in str(settings.OPENAI_API_KEY) else "gpt-4o",
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_prompt
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Analiza este componente del vehículo.\n{question_context}"
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{image_b64}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
max_tokens=500
|
||||
)
|
||||
|
||||
ai_response = response.choices[0].message.content
|
||||
|
||||
# Intentar parsear como JSON, si falla, usar texto plano
|
||||
try:
|
||||
import json
|
||||
analysis = json.loads(ai_response)
|
||||
except:
|
||||
# Si no es JSON válido, crear estructura básica
|
||||
analysis = {
|
||||
"status": "ok",
|
||||
"observations": ai_response,
|
||||
"recommendation": "Revisar manualmente",
|
||||
"confidence": 0.7
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"analysis": analysis,
|
||||
"raw_response": ai_response,
|
||||
"model": "gpt-4-vision"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error en análisis AI: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": "Error analyzing image with AI"
|
||||
}
|
||||
|
||||
|
||||
# ============= HEALTH CHECK =============
|
||||
@app.get("/")
|
||||
def root():
|
||||
|
||||
@@ -144,3 +144,15 @@ class MediaFile(Base):
|
||||
|
||||
# Relationships
|
||||
answer = relationship("Answer", back_populates="media_files")
|
||||
|
||||
|
||||
class AIConfiguration(Base):
|
||||
__tablename__ = "ai_configurations"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
provider = Column(String(50), nullable=False) # openai, gemini
|
||||
api_key = Column(Text, nullable=False)
|
||||
model_name = Column(String(100), nullable=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
14
backend/app/models_ai.py
Normal file
14
backend/app/models_ai.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Text
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class AIConfiguration(Base):
|
||||
__tablename__ = "ai_configurations"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
provider = Column(String(50), nullable=False) # openai, gemini
|
||||
api_key = Column(Text, nullable=False)
|
||||
model_name = Column(String(100), nullable=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
@@ -175,3 +175,33 @@ class InspectionDetail(Inspection):
|
||||
checklist: ChecklistWithQuestions
|
||||
mechanic: User
|
||||
answers: List[AnswerWithMedia] = []
|
||||
|
||||
|
||||
# AI Configuration Schemas
|
||||
class AIConfigurationBase(BaseModel):
|
||||
provider: str # openai, gemini
|
||||
api_key: str
|
||||
model_name: str
|
||||
|
||||
class AIConfigurationCreate(AIConfigurationBase):
|
||||
pass
|
||||
|
||||
class AIConfigurationUpdate(BaseModel):
|
||||
provider: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
model_name: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
class AIConfiguration(AIConfigurationBase):
|
||||
id: int
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class AIModelInfo(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
provider: str
|
||||
description: Optional[str] = None
|
||||
|
||||
61
backend/init_db.py
Normal file
61
backend/init_db.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script para inicializar la base de datos con usuarios de prueba
|
||||
"""
|
||||
from app.core.database import SessionLocal, engine, Base
|
||||
from app.core.security import get_password_hash
|
||||
from app.models import User
|
||||
from sqlalchemy import text
|
||||
|
||||
def init_db():
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# Verificar conexión
|
||||
db.execute(text("SELECT 1"))
|
||||
print("✓ Conexión a la base de datos exitosa")
|
||||
|
||||
# Verificar si ya existen usuarios
|
||||
existing_users = db.query(User).count()
|
||||
if existing_users > 0:
|
||||
print(f"⚠ Ya existen {existing_users} usuario(s) en la base de datos")
|
||||
return
|
||||
|
||||
# Crear usuario administrador
|
||||
admin = User(
|
||||
username="admin",
|
||||
email="admin@checklist.com",
|
||||
full_name="Administrador",
|
||||
password_hash=get_password_hash("admin123"),
|
||||
role="admin",
|
||||
is_active=True
|
||||
)
|
||||
db.add(admin)
|
||||
|
||||
# Crear usuario mecánico de prueba
|
||||
mechanic = User(
|
||||
username="mechanic",
|
||||
email="mechanic@checklist.com",
|
||||
full_name="Mecánico de Prueba",
|
||||
password_hash=get_password_hash("mechanic123"),
|
||||
role="mechanic",
|
||||
is_active=True
|
||||
)
|
||||
db.add(mechanic)
|
||||
|
||||
db.commit()
|
||||
|
||||
print("✓ Usuarios creados exitosamente:")
|
||||
print(" - Admin: username='admin', password='admin123'")
|
||||
print(" - Mechanic: username='mechanic', password='mechanic123'")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error al inicializar la base de datos: {e}")
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Inicializando base de datos...")
|
||||
init_db()
|
||||
print("¡Inicialización completada!")
|
||||
@@ -11,6 +11,7 @@ passlib==1.7.4
|
||||
bcrypt==4.0.1
|
||||
python-multipart==0.0.6
|
||||
openai==1.10.0
|
||||
google-generativeai==0.3.2
|
||||
Pillow==10.2.0
|
||||
reportlab==4.0.9
|
||||
python-dotenv==1.0.0
|
||||
|
||||
Reference in New Issue
Block a user