Agrega backend FastAPI al proyecto
This commit is contained in:
0
recolector_backend/app/routers/__init__.py
Normal file
0
recolector_backend/app/routers/__init__.py
Normal file
110
recolector_backend/app/routers/admin.py
Normal file
110
recolector_backend/app/routers/admin.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from sqlalchemy import func
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Alert, Rating, Report, Route, User
|
||||
from app.schemas import (
|
||||
AlertOut,
|
||||
AssignOperatorIn,
|
||||
DashboardOut,
|
||||
ReportOut,
|
||||
ReportStatusUpdate,
|
||||
RouteOut,
|
||||
UserOut,
|
||||
)
|
||||
from app.security import require_role
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["Admin"])
|
||||
|
||||
|
||||
@router.get("/dashboard", response_model=DashboardOut)
|
||||
def dashboard(db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
routes_total = db.query(Route).count()
|
||||
active_routes = db.query(Route).filter(Route.status.in_(["EN_RUTA", "RETRASO", "AVERIA", "INCIDENCIA"])).count()
|
||||
trucks_total = db.query(func.count(func.distinct(Route.truck_id))).scalar() or 0
|
||||
operators_total = db.query(User).filter(User.role == "operador").count()
|
||||
alerts_open = db.query(Alert).filter(Alert.status != "CERRADA").count()
|
||||
reports_open = db.query(Report).filter(Report.status.in_(["NUEVO", "EN_REVISION"])).count()
|
||||
avg = db.query(func.avg(Rating.stars)).scalar()
|
||||
|
||||
return DashboardOut(
|
||||
routes_total=routes_total,
|
||||
active_routes=active_routes,
|
||||
trucks_total=trucks_total,
|
||||
operators_total=operators_total,
|
||||
alerts_open=alerts_open,
|
||||
reports_open=reports_open,
|
||||
average_rating=round(float(avg or 0), 2),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[UserOut])
|
||||
def users(db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
return db.query(User).order_by(User.role.asc(), User.name.asc()).all()
|
||||
|
||||
|
||||
@router.get("/operators", response_model=list[UserOut])
|
||||
def operators(db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
return db.query(User).filter(User.role == "operador").order_by(User.name.asc()).all()
|
||||
|
||||
|
||||
@router.get("/routes", response_model=list[RouteOut])
|
||||
def routes(db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
return db.query(Route).order_by(Route.route_id.asc()).all()
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/assign-operator", response_model=RouteOut)
|
||||
def assign_operator(
|
||||
route_id: str,
|
||||
payload: AssignOperatorIn,
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(require_role("admin")),
|
||||
):
|
||||
route = db.get(Route, route_id)
|
||||
if not route:
|
||||
raise HTTPException(status_code=404, detail="Ruta no encontrada")
|
||||
operator = db.get(User, payload.operator_id)
|
||||
if not operator or operator.role != "operador":
|
||||
raise HTTPException(status_code=422, detail="Operador inválido")
|
||||
route.assigned_operator_id = operator.id
|
||||
db.commit()
|
||||
db.refresh(route)
|
||||
return route
|
||||
|
||||
|
||||
@router.get("/alerts", response_model=list[AlertOut])
|
||||
def alerts(db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
return db.query(Alert).order_by(Alert.created_at.desc()).limit(100).all()
|
||||
|
||||
|
||||
@router.patch("/alerts/{alert_id}/close", response_model=AlertOut)
|
||||
def close_alert(alert_id: int, db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
alert = db.get(Alert, alert_id)
|
||||
if not alert:
|
||||
raise HTTPException(status_code=404, detail="Alerta no encontrada")
|
||||
alert.status = "CERRADA"
|
||||
db.commit()
|
||||
db.refresh(alert)
|
||||
return alert
|
||||
|
||||
|
||||
@router.get("/reports", response_model=list[ReportOut])
|
||||
def reports(db: Session = Depends(get_db), admin: User = Depends(require_role("admin"))):
|
||||
return db.query(Report).order_by(Report.created_at.desc()).limit(100).all()
|
||||
|
||||
|
||||
@router.patch("/reports/{report_id}/status", response_model=ReportOut)
|
||||
def update_report_status(
|
||||
report_id: int,
|
||||
payload: ReportStatusUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(require_role("admin")),
|
||||
):
|
||||
report = db.get(Report, report_id)
|
||||
if not report:
|
||||
raise HTTPException(status_code=404, detail="Reporte no encontrado")
|
||||
report.status = payload.status
|
||||
db.commit()
|
||||
db.refresh(report)
|
||||
return report
|
||||
108
recolector_backend/app/routers/auth.py
Normal file
108
recolector_backend/app/routers/auth.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import User
|
||||
from app.schemas import RegisterIn, TokenOut, UserOut
|
||||
from app.security import (
|
||||
create_access_token,
|
||||
get_current_user,
|
||||
hash_password,
|
||||
validate_email,
|
||||
verify_password,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["Auth"])
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenOut)
|
||||
async def login(request: Request, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Login compatible con:
|
||||
1. Swagger Authorize OAuth2 Password:
|
||||
username=correo
|
||||
password=contraseña
|
||||
|
||||
2. Flutter / Postman JSON:
|
||||
{
|
||||
"email": "admin@demo.com",
|
||||
"password": "123456"
|
||||
}
|
||||
"""
|
||||
|
||||
content_type = request.headers.get("content-type", "")
|
||||
|
||||
email = ""
|
||||
password = ""
|
||||
|
||||
if "application/x-www-form-urlencoded" in content_type or "multipart/form-data" in content_type:
|
||||
form = await request.form()
|
||||
email = str(form.get("username") or form.get("email") or "").strip()
|
||||
password = str(form.get("password") or "").strip()
|
||||
else:
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception:
|
||||
payload = {}
|
||||
|
||||
email = str(payload.get("email") or payload.get("username") or "").strip()
|
||||
password = str(payload.get("password") or "").strip()
|
||||
|
||||
if not email or not password:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail="Debes enviar correo y contraseña.",
|
||||
)
|
||||
|
||||
email = validate_email(email)
|
||||
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
|
||||
if not user or not verify_password(password, user.password_hash):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Correo o contraseña incorrectos",
|
||||
)
|
||||
|
||||
token = create_access_token(user)
|
||||
|
||||
return TokenOut(
|
||||
access_token=token,
|
||||
token_type="bearer",
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/register", response_model=TokenOut)
|
||||
def register(payload: RegisterIn, db: Session = Depends(get_db)):
|
||||
email = validate_email(payload.email)
|
||||
role = payload.role.strip().lower()
|
||||
|
||||
if role not in {"ciudadano", "operador", "admin"}:
|
||||
raise HTTPException(status_code=422, detail="Rol inválido")
|
||||
|
||||
if db.query(User).filter(User.email == email).first():
|
||||
raise HTTPException(status_code=409, detail="Ese correo ya está registrado")
|
||||
|
||||
user = User(
|
||||
name=payload.name.strip(),
|
||||
email=email,
|
||||
phone=payload.phone,
|
||||
password_hash=hash_password(payload.password),
|
||||
role=role,
|
||||
)
|
||||
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
return TokenOut(
|
||||
access_token=create_access_token(user),
|
||||
token_type="bearer",
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserOut)
|
||||
def me(current_user: User = Depends(get_current_user)):
|
||||
return current_user
|
||||
160
recolector_backend/app/routers/citizen.py
Normal file
160
recolector_backend/app/routers/citizen.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Alert, Colonia, Domicilio, Rating, Report, Route, User
|
||||
from app.schemas import (
|
||||
AlertOut,
|
||||
DomicilioCreate,
|
||||
DomicilioOut,
|
||||
EtaOut,
|
||||
RatingCreate,
|
||||
RatingOut,
|
||||
ReportCreate,
|
||||
ReportOut,
|
||||
)
|
||||
from app.security import get_current_user, require_role, validate_address
|
||||
|
||||
router = APIRouter(prefix="/citizen", tags=["Citizen"])
|
||||
|
||||
|
||||
def normalize(text: str) -> str:
|
||||
repl = str.maketrans("áéíóúüÁÉÍÓÚÜ", "aeiouuAEIOUU")
|
||||
return text.strip().translate(repl).lower()
|
||||
|
||||
|
||||
def find_colonia(db: Session, colonia_name: str) -> Colonia:
|
||||
wanted = normalize(colonia_name)
|
||||
colonias = db.query(Colonia).all()
|
||||
for c in colonias:
|
||||
if normalize(c.colonia) == wanted:
|
||||
return c
|
||||
raise HTTPException(status_code=422, detail="Colonia no válida o fuera de cobertura")
|
||||
|
||||
|
||||
def ensure_owner(db: Session, domicilio_id: int, user: User) -> Domicilio:
|
||||
domicilio = db.get(Domicilio, domicilio_id)
|
||||
if not domicilio or domicilio.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Domicilio no encontrado")
|
||||
return domicilio
|
||||
|
||||
|
||||
@router.get("/domicilios", response_model=list[DomicilioOut])
|
||||
def list_domicilios(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("ciudadano")),
|
||||
):
|
||||
return db.query(Domicilio).filter(Domicilio.user_id == current_user.id).all()
|
||||
|
||||
|
||||
@router.post("/domicilios", response_model=DomicilioOut)
|
||||
def create_domicilio(
|
||||
payload: DomicilioCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("ciudadano")),
|
||||
):
|
||||
direccion = validate_address(payload.direccion)
|
||||
colonia = find_colonia(db, payload.colonia)
|
||||
|
||||
domicilio = Domicilio(
|
||||
user_id=current_user.id,
|
||||
tipo=payload.tipo.strip() or "Casa principal",
|
||||
direccion=direccion,
|
||||
colonia=colonia.colonia,
|
||||
lat=payload.lat,
|
||||
lng=payload.lng,
|
||||
route_id=colonia.route_id,
|
||||
)
|
||||
db.add(domicilio)
|
||||
db.commit()
|
||||
db.refresh(domicilio)
|
||||
return domicilio
|
||||
|
||||
|
||||
@router.get("/domicilios/{domicilio_id}/eta", response_model=EtaOut)
|
||||
def get_eta(
|
||||
domicilio_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("ciudadano")),
|
||||
):
|
||||
domicilio = ensure_owner(db, domicilio_id, current_user)
|
||||
route = db.get(Route, domicilio.route_id)
|
||||
colonia = db.query(Colonia).filter(Colonia.colonia == domicilio.colonia).first()
|
||||
if not route or not colonia:
|
||||
raise HTTPException(status_code=404, detail="Ruta no encontrada")
|
||||
|
||||
if route.current_position_id >= 8:
|
||||
eta = "El servicio de tu sector ya finalizó."
|
||||
elif route.current_position_id >= 4:
|
||||
eta = "El camión llegará a tu zona en aproximadamente 15 minutos."
|
||||
elif route.status in {"RETRASO", "AVERIA"}:
|
||||
eta = "Hay una incidencia operativa. Revisa tus alertas antes de sacar tus residuos."
|
||||
else:
|
||||
eta = f"Ventana estimada de recolección: {colonia.horario_estimado}."
|
||||
|
||||
return EtaOut(
|
||||
domicilio_id=domicilio.id,
|
||||
route_id=route.route_id,
|
||||
route_name=route.name,
|
||||
truck_id=route.truck_id,
|
||||
colonia=domicilio.colonia,
|
||||
horario_estimado=colonia.horario_estimado,
|
||||
eta_message=eta,
|
||||
current_position_id=route.current_position_id,
|
||||
privacy_note="Privacidad por diseño: no se expone el mapa ni la ubicación exacta del camión.",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/alerts", response_model=list[AlertOut])
|
||||
def my_alerts(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("ciudadano")),
|
||||
):
|
||||
route_ids = [d.route_id for d in db.query(Domicilio).filter(Domicilio.user_id == current_user.id).all()]
|
||||
if not route_ids:
|
||||
return []
|
||||
return (
|
||||
db.query(Alert)
|
||||
.filter(Alert.route_id.in_(route_ids))
|
||||
.order_by(Alert.created_at.desc())
|
||||
.limit(20)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/reports", response_model=ReportOut)
|
||||
def create_report(
|
||||
payload: ReportCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("ciudadano")),
|
||||
):
|
||||
ensure_owner(db, payload.domicilio_id, current_user)
|
||||
report = Report(
|
||||
user_id=current_user.id,
|
||||
domicilio_id=payload.domicilio_id,
|
||||
type=payload.type.strip(),
|
||||
comment=payload.comment.strip(),
|
||||
)
|
||||
db.add(report)
|
||||
db.commit()
|
||||
db.refresh(report)
|
||||
return report
|
||||
|
||||
|
||||
@router.post("/ratings", response_model=RatingOut)
|
||||
def create_rating(
|
||||
payload: RatingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("ciudadano")),
|
||||
):
|
||||
ensure_owner(db, payload.domicilio_id, current_user)
|
||||
rating = Rating(
|
||||
user_id=current_user.id,
|
||||
domicilio_id=payload.domicilio_id,
|
||||
stars=payload.stars,
|
||||
comment=payload.comment,
|
||||
)
|
||||
db.add(rating)
|
||||
db.commit()
|
||||
db.refresh(rating)
|
||||
return rating
|
||||
242
recolector_backend/app/routers/operator.py
Normal file
242
recolector_backend/app/routers/operator.py
Normal file
@@ -0,0 +1,242 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Alert, Route, User
|
||||
from app.schemas import AlertOut, OperatorActionOut, RouteDetailOut, RouteOut
|
||||
from app.security import require_role
|
||||
|
||||
router = APIRouter(prefix="/operator", tags=["Operator"])
|
||||
|
||||
|
||||
def get_assigned_route(db: Session, route_id: str, operator: User) -> Route:
|
||||
route = (
|
||||
db.query(Route)
|
||||
.options(selectinload(Route.positions))
|
||||
.filter(Route.route_id == route_id)
|
||||
.first()
|
||||
)
|
||||
if not route:
|
||||
raise HTTPException(status_code=404, detail="Ruta no encontrada")
|
||||
if route.assigned_operator_id != operator.id:
|
||||
raise HTTPException(status_code=403, detail="Esta ruta no está asignada a este operador")
|
||||
return route
|
||||
|
||||
|
||||
def create_alert(
|
||||
db: Session,
|
||||
route: Route,
|
||||
operator: User,
|
||||
type_: str,
|
||||
title: str,
|
||||
message: str,
|
||||
priority: int,
|
||||
) -> Alert:
|
||||
alert = Alert(
|
||||
type=type_,
|
||||
title=title,
|
||||
message=message,
|
||||
route_id=route.route_id,
|
||||
truck_id=route.truck_id,
|
||||
operator_id=operator.id,
|
||||
priority=priority,
|
||||
status="NUEVA",
|
||||
)
|
||||
db.add(alert)
|
||||
db.flush()
|
||||
return alert
|
||||
|
||||
|
||||
def action_response(db: Session, route: Route, alert: Alert | None, message: str) -> OperatorActionOut:
|
||||
db.commit()
|
||||
db.refresh(route)
|
||||
if alert:
|
||||
db.refresh(alert)
|
||||
return OperatorActionOut(ok=True, route=route, alert=alert, message=message)
|
||||
|
||||
|
||||
@router.get("/routes", response_model=list[RouteOut])
|
||||
def my_routes(
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
return (
|
||||
db.query(Route)
|
||||
.filter(Route.assigned_operator_id == operator.id)
|
||||
.order_by(Route.route_id.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
@router.get("/routes/{route_id}", response_model=RouteDetailOut)
|
||||
def route_detail(
|
||||
route_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
route.positions = sorted(route.positions, key=lambda p: p.position_id)
|
||||
return route
|
||||
|
||||
|
||||
@router.get("/alerts", response_model=list[AlertOut])
|
||||
def my_sent_alerts(
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
return (
|
||||
db.query(Alert)
|
||||
.filter(Alert.operator_id == operator.id)
|
||||
.order_by(Alert.created_at.desc())
|
||||
.limit(30)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/start", response_model=OperatorActionOut)
|
||||
def start_route(
|
||||
route_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
route.status = "EN_RUTA"
|
||||
route.current_position_id = 2
|
||||
route.updated_at = datetime.utcnow()
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"ROUTE_START",
|
||||
"Ruta iniciada",
|
||||
f"El camión {route.truck_id} inició la ruta {route.route_id}.",
|
||||
1,
|
||||
)
|
||||
return action_response(db, route, alert, "Jornada iniciada correctamente")
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/advance/{position_id}", response_model=OperatorActionOut)
|
||||
def advance_route(
|
||||
route_id: str,
|
||||
position_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
valid_positions = {p.position_id for p in route.positions}
|
||||
if position_id not in valid_positions:
|
||||
raise HTTPException(status_code=422, detail="positionId inválido para esta ruta")
|
||||
|
||||
route.current_position_id = position_id
|
||||
route.status = "EN_RUTA" if position_id < 8 else "FINALIZADA"
|
||||
route.updated_at = datetime.utcnow()
|
||||
|
||||
alert = None
|
||||
if position_id == 4:
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"TRUCK_PROXIMITY",
|
||||
"Camión cercano",
|
||||
f"El camión {route.truck_id} está a menos de 15 minutos de la zona asignada.",
|
||||
2,
|
||||
)
|
||||
elif position_id == 8:
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"ROUTE_COMPLETED",
|
||||
"Servicio finalizado",
|
||||
f"El camión {route.truck_id} finalizó la ruta {route.route_id}.",
|
||||
1,
|
||||
)
|
||||
|
||||
return action_response(db, route, alert, "Avance de ruta actualizado")
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/delay", response_model=OperatorActionOut)
|
||||
def delay_route(
|
||||
route_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
route.status = "RETRASO"
|
||||
route.updated_at = datetime.utcnow()
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"DELAY",
|
||||
"Retraso operativo",
|
||||
f"La ruta {route.route_id} presenta un retraso aproximado de 25 minutos.",
|
||||
2,
|
||||
)
|
||||
return action_response(db, route, alert, "Retraso reportado")
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/breakdown", response_model=OperatorActionOut)
|
||||
def breakdown_route(
|
||||
route_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
route.status = "AVERIA"
|
||||
route.updated_at = datetime.utcnow()
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"MECHANICAL_FAILURE",
|
||||
"Avería mecánica",
|
||||
f"El camión {route.truck_id} presenta falla mecánica. Se requiere apoyo logístico.",
|
||||
3,
|
||||
)
|
||||
return action_response(db, route, alert, "Avería reportada")
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/incident", response_model=OperatorActionOut)
|
||||
def incident_route(
|
||||
route_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
route.status = "INCIDENCIA"
|
||||
route.updated_at = datetime.utcnow()
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"INCIDENT",
|
||||
"Incidencia en ruta",
|
||||
f"Se registró una incidencia menor en {route.route_id}: obstrucción vial o exceso de residuos.",
|
||||
2,
|
||||
)
|
||||
return action_response(db, route, alert, "Incidencia reportada")
|
||||
|
||||
|
||||
@router.post("/routes/{route_id}/complete", response_model=OperatorActionOut)
|
||||
def complete_route(
|
||||
route_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
operator: User = Depends(require_role("operador")),
|
||||
):
|
||||
route = get_assigned_route(db, route_id, operator)
|
||||
route.status = "FINALIZADA"
|
||||
route.current_position_id = 8
|
||||
route.updated_at = datetime.utcnow()
|
||||
alert = create_alert(
|
||||
db,
|
||||
route,
|
||||
operator,
|
||||
"ROUTE_COMPLETED",
|
||||
"Servicio finalizado",
|
||||
f"El operador finalizó la ruta {route.route_id}.",
|
||||
1,
|
||||
)
|
||||
return action_response(db, route, alert, "Ruta finalizada")
|
||||
49
recolector_backend/app/routers/public.py
Normal file
49
recolector_backend/app/routers/public.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Colonia, Route
|
||||
from app.schemas import ColoniaOut, RouteOut
|
||||
|
||||
router = APIRouter(prefix="/public", tags=["Public"])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def health():
|
||||
return {"ok": True, "message": "Recolector Inteligente API funcionando"}
|
||||
|
||||
|
||||
@router.get("/colonias", response_model=list[ColoniaOut])
|
||||
def colonias(db: Session = Depends(get_db)):
|
||||
return db.query(Colonia).order_by(Colonia.colonia.asc()).all()
|
||||
|
||||
|
||||
@router.get("/routes", response_model=list[RouteOut])
|
||||
def routes(db: Session = Depends(get_db)):
|
||||
return db.query(Route).order_by(Route.route_id.asc()).all()
|
||||
|
||||
|
||||
@router.get("/guide")
|
||||
def guide():
|
||||
return [
|
||||
{
|
||||
"categoria": "Orgánicos",
|
||||
"ejemplos": "Comida, frutas, verduras, restos de café y hojas",
|
||||
"detalle": "Pueden convertirse en composta y reducen malos olores si se separan.",
|
||||
},
|
||||
{
|
||||
"categoria": "Reciclables",
|
||||
"ejemplos": "Cartón, plástico, vidrio, latas y papel limpio",
|
||||
"detalle": "Deben entregarse limpios y secos para poder reutilizarse.",
|
||||
},
|
||||
{
|
||||
"categoria": "Sanitarios",
|
||||
"ejemplos": "Papel higiénico, pañales, toallas sanitarias y cubrebocas",
|
||||
"detalle": "Deben ir en bolsa cerrada porque pueden representar riesgo sanitario.",
|
||||
},
|
||||
{
|
||||
"categoria": "Especiales",
|
||||
"ejemplos": "Pilas, electrónicos, focos, aceite y medicamentos",
|
||||
"detalle": "No deben mezclarse con basura común; requieren centros de acopio.",
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user