añadido los estados de reporte

This commit is contained in:
2026-04-19 19:08:43 -06:00
parent 6083ab34ca
commit 30efa0e098
17 changed files with 1173 additions and 1047 deletions

View File

@@ -35,6 +35,11 @@ class ReportRepository(ABC):
"""Actualiza la visibilidad de un reporte"""
pass
@abstractmethod
def update_estado(self, report_id: str, new_estado: str) -> None:
"""Actualiza el estado de un reporte"""
pass
@abstractmethod
def delete(self, report_id: str) -> bool:
"""Elimina un reporte"""

View File

@@ -19,7 +19,8 @@ class CreateReport:
def execute(self, id_usuario: int, tipo_reporte: int, descripcion: str,
ubicacion: Optional[str] = None, lat: Optional[float] = None,
lng: Optional[float] = None, image_filename: Optional[str] = None) -> Dict[str, Any]:
lng: Optional[float] = None, image_filename: Optional[str] = None,
estado: str = "en proceso") -> Dict[str, Any]:
"""
Sends a create report message to RabbitMQ.
Valida previamente:
@@ -44,6 +45,14 @@ class CreateReport:
"message": "El tipo de reporte debe estar entre 1 y 5"
}
# Validación: estado válido
estados_validos = ["en proceso", "no resuelto", "resuelto"]
if estado not in estados_validos:
return {
"status": "error",
"message": f"El estado del reporte debe ser uno de: {', '.join(estados_validos)}"
}
# Validación: usuario existe (CRÍTICO)
try:
user = self.user_repo.find_by_id(id_usuario)
@@ -73,6 +82,7 @@ class CreateReport:
lng=lng,
image_filename=image_filename,
visibilidad=50.0, # Visibilidad inicial neutral
estado=estado,
fecha_creacion=fecha_creacion.isoformat()
)
@@ -244,3 +254,57 @@ class DeleteReport:
"status": "error",
"message": "Error al enviar eliminación del reporte a la cola de procesamiento"
}
class UpdateReportStatus:
"""Use case para actualizar el estado de un reporte"""
def __init__(self, repo: ReportRepository):
if not isinstance(repo, ReportRepository):
raise TypeError("repo must implement ReportRepository")
self.repo = repo
def execute(self, report_id: str, new_estado: str) -> Dict[str, Any]:
"""
Actualiza el estado de un reporte.
Valida previamente:
- Reporte existe
- Estado es válido
Returns:
Dictionary with status and message
"""
# Validación: estado válido
estados_validos = ["en proceso", "no resuelto", "resuelto"]
if new_estado not in estados_validos:
return {
"status": "error",
"message": f"El estado del reporte debe ser uno de: {', '.join(estados_validos)}"
}
# Validación: reporte existe
try:
report = self.repo.find_by_id(report_id)
if not report:
return {
"status": "error",
"message": f"Reporte con ID {report_id} no existe"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al buscar reporte: {str(e)}"
}
# Actualizar estado
try:
self.repo.update_estado(report_id, new_estado)
return {
"status": "success",
"message": f"Estado del reporte actualizado a '{new_estado}'",
"id_reporte": report_id,
"nuevo_estado": new_estado
}
except Exception as e:
return {
"status": "error",
"message": f"Error al actualizar estado del reporte: {str(e)}"
}

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from typing import Optional, Literal
@dataclass
class Report:
@@ -14,4 +14,5 @@ class Report:
lng: Optional[float] = None
image_filename: Optional[str] = None # Nombre del archivo de imagen WebP
visibilidad: float = 0.0 # 0-100 (puntuación comunitaria)
estado: Literal["en proceso", "no resuelto", "resuelto"] = "en proceso" # Estado del reporte
fecha_creacion: Optional[datetime] = None

View File

@@ -23,6 +23,7 @@ class ReportRepositoryMongo(ReportRepository):
"lng": report.lng,
"image_filename": report.image_filename,
"visibilidad": report.visibilidad,
"estado": report.estado,
"fecha_creacion": report.fecha_creacion or datetime.utcnow()
}
result = self.collection.insert_one(report_dict)
@@ -59,6 +60,13 @@ class ReportRepositoryMongo(ReportRepository):
{"$set": {"visibilidad": new_visibility}}
)
def update_estado(self, report_id: str, new_estado: str) -> None:
"""Actualiza el estado de un reporte"""
self.collection.update_one(
{"id_reporte": report_id},
{"$set": {"estado": new_estado}}
)
def delete(self, report_id: str) -> bool:
"""Elimina un reporte"""
result = self.collection.delete_one({"id_reporte": report_id})
@@ -83,5 +91,6 @@ class ReportRepositoryMongo(ReportRepository):
lng=doc.get("lng"),
image_filename=doc.get("image_filename"),
visibilidad=doc.get("visibilidad"),
estado=doc.get("estado", "en proceso"),
fecha_creacion=doc.get("fecha_creacion")
)

View File

@@ -65,6 +65,7 @@ class ReportMessage:
lng: Optional[float] = None
image_filename: Optional[str] = None # Nombre del archivo de imagen guardado
visibilidad: Optional[float] = None
estado: Optional[str] = None # Estado del reporte: "en proceso", "no resuelto", "resuelto"
fecha_creacion: Optional[str] = None # ISO format datetime string
penalize_author: Optional[bool] = None # For update_visibility event

View File

@@ -1,8 +1,8 @@
from fastapi import APIRouter, HTTPException, status, UploadFile, File, Form
from infrastructure.api.reports.schemas import ReportCreateRequest, ReportUpdateVisibilityRequest, ReportResponse
from infrastructure.api.reports.schemas import ReportCreateRequest, ReportUpdateVisibilityRequest, ReportUpdateStatusRequest, ReportResponse
from application.services.report_services import (
CreateReport, GetReportById, GetReportsByUser, ListAllReports,
UpdateReportVisibility, GetShadowbannedReports, DeleteReport
UpdateReportVisibility, GetShadowbannedReports, DeleteReport, UpdateReportStatus
)
from infrastructure.adapters.persistence.report_repository_mongo import ReportRepositoryMongo
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
@@ -28,6 +28,7 @@ def _report_to_response(report) -> dict:
"lng": report.lng,
"image_url": image_storage.get_image_url(report.image_filename) if report.image_filename else None,
"visibilidad": report.visibilidad,
"estado": report.estado,
"fecha_creacion": report.fecha_creacion
}
@@ -39,6 +40,7 @@ async def create_report(
ubicacion: Optional[str] = Form(None),
lat: Optional[float] = Form(None),
lng: Optional[float] = Form(None),
estado: str = Form("en proceso"),
file: Optional[UploadFile] = File(None)
):
"""Crea un nuevo reporte - envía a cola de procesamiento con validaciones previas"""
@@ -57,7 +59,8 @@ async def create_report(
ubicacion=ubicacion,
lat=lat,
lng=lng,
image_filename=image_filename
image_filename=image_filename,
estado=estado
)
if result["status"] == "error":
@@ -225,3 +228,40 @@ async def delete_report(report_id: str):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.put("/{report_id}/status", status_code=status.HTTP_200_OK)
async def update_report_status(report_id: str, status_data: ReportUpdateStatusRequest):
"""Actualiza el estado de un reporte"""
try:
update_use_case = UpdateReportStatus(report_repo)
result = update_use_case.execute(
report_id=report_id,
new_estado=status_data.estado
)
if result["status"] == "error":
message = result["message"]
if "no existe" in message:
# 404 Not Found: reporte no existe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 200 OK: estado actualizado correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al actualizar estado del reporte {report_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)

View File

@@ -1,6 +1,6 @@
from pydantic import BaseModel
from datetime import datetime
from typing import Optional
from typing import Optional, Literal
from fastapi import Form, UploadFile, File
class ReportCreateRequest(BaseModel):
@@ -11,6 +11,7 @@ class ReportCreateRequest(BaseModel):
ubicacion: Optional[str] = None
lat: Optional[float] = None
lng: Optional[float] = None
estado: Literal["en proceso", "no resuelto", "resuelto"] = "en proceso" # Estado del reporte
# file se recibe como UploadFile en el endpoint, no en el modelo
class ReportUpdateVisibilityRequest(BaseModel):
@@ -18,6 +19,10 @@ class ReportUpdateVisibilityRequest(BaseModel):
new_visibility: float
penalize_author: bool = False
class ReportUpdateStatusRequest(BaseModel):
"""Solicitud para actualizar el estado de un reporte"""
estado: Literal["en proceso", "no resuelto", "resuelto"]
class ReportResponse(BaseModel):
"""Respuesta con datos de reporte"""
id_reporte: str
@@ -29,6 +34,7 @@ class ReportResponse(BaseModel):
lng: Optional[float]
image_url: Optional[str] = None # URL pública para acceder a la imagen
visibilidad: float
estado: Literal["en proceso", "no resuelto", "resuelto"] = "en proceso" # Estado del reporte
fecha_creacion: Optional[datetime]
class Config: