From d691b71c44e023f2b6483903f63bfb048288e3dd Mon Sep 17 00:00:00 2001 From: icamarillo Date: Tue, 17 Mar 2026 11:09:30 -0600 Subject: [PATCH] Bckn funcion --- add_participant.py | 48 ----- add_relation.py | 21 --- backend/app/api/schemas/issue.py | 53 ++++++ backend/app/api/v1/endpoints/tickets.py | 178 ++++++++++++++++++ backend/app/main.py | 1 + backend/app/models/__init__.py | 4 +- backend/app/models/issue.py | 102 ++++++++++ backend/app/models/ticket.py | 6 + .../b2dcb926e091_add_ticket_issues.py | 63 +++++++ check_schema.py | 3 - debug_tenant.py | 19 -- fix_categories.py | 28 --- fix_delete.py | 15 -- fix_delete2.py | 14 -- fix_router.py | 34 ---- fix_schema.py | 30 --- fix_usuarios.py | 11 -- 17 files changed, 406 insertions(+), 224 deletions(-) delete mode 100644 add_participant.py delete mode 100644 add_relation.py create mode 100644 backend/app/api/schemas/issue.py create mode 100644 backend/app/models/issue.py create mode 100644 backend/migrations/versions/b2dcb926e091_add_ticket_issues.py delete mode 100644 check_schema.py delete mode 100644 debug_tenant.py delete mode 100644 fix_categories.py delete mode 100644 fix_delete.py delete mode 100644 fix_delete2.py delete mode 100644 fix_router.py delete mode 100644 fix_schema.py delete mode 100644 fix_usuarios.py diff --git a/add_participant.py b/add_participant.py deleted file mode 100644 index fa4006f..0000000 --- a/add_participant.py +++ /dev/null @@ -1,48 +0,0 @@ -import asyncio -from app.core.database import AsyncSessionLocal, Base, engine -from app.models.ticket import Ticket -from sqlalchemy import String, ForeignKey, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column, relationship -from app.core.database import GUID -import uuid - -# Agregar modelo al archivo ticket.py -new_model = ''' - -class TicketParticipant(Base): - """Participantes asignados a un ticket""" - __tablename__ = "ticket_participants" - - ticket_id: Mapped[uuid.UUID] = mapped_column( - GUID(), - ForeignKey("tickets.id", ondelete="CASCADE"), - nullable=False - ) - user_id: Mapped[uuid.UUID] = mapped_column( - GUID(), - ForeignKey("users.id", ondelete="CASCADE"), - nullable=False - ) - role: Mapped[str] = mapped_column(String(50), nullable=False, default="participant") - - ticket: Mapped["Ticket"] = relationship("Ticket", back_populates="participants") - user: Mapped["User"] = relationship("User") - - __table_args__ = ( - UniqueConstraint("ticket_id", "user_id", name="uq_ticket_participant"), - ) - - def __repr__(self): - return f"" -''' - -with open("/app/app/models/ticket.py", "r") as f: - content = f.read() - -if "TicketParticipant" in content: - print("Ya existe TicketParticipant") -else: - content += new_model - with open("/app/app/models/ticket.py", "w") as f: - f.write(content) - print("OK: TicketParticipant agregado") diff --git a/add_relation.py b/add_relation.py deleted file mode 100644 index aa184b3..0000000 --- a/add_relation.py +++ /dev/null @@ -1,21 +0,0 @@ -with open("/app/app/models/ticket.py", "r") as f: - content = f.read() - -old = ' def __repr__(self) -> str:\n return f""' -new = ''' participants: Mapped[list["TicketParticipant"]] = relationship( - "TicketParticipant", - back_populates="ticket", - cascade="all, delete-orphan" - ) - - def __repr__(self) -> str: - return f""''' - -if old in content: - content = content.replace(old, new) - print("OK: relacion participants agregada") -else: - print("ERROR: bloque no encontrado") - -with open("/app/app/models/ticket.py", "w") as f: - f.write(content) diff --git a/backend/app/api/schemas/issue.py b/backend/app/api/schemas/issue.py new file mode 100644 index 0000000..e478710 --- /dev/null +++ b/backend/app/api/schemas/issue.py @@ -0,0 +1,53 @@ +"""Schemas para TicketIssue""" +from pydantic import BaseModel, field_validator +from typing import Optional, List +from datetime import datetime +import uuid + + +class TaggedUserBasic(BaseModel): + id: uuid.UUID + full_name: str + email: str + + class Config: + from_attributes = True + + +class IssueCreate(BaseModel): + content: str + priority: str = "MEDIUM" + tagged_user_ids: List[uuid.UUID] = [] + + @field_validator("priority") + @classmethod + def validate_priority(cls, v: str) -> str: + valid = {"LOW", "MEDIUM", "HIGH", "URGENT"} + if v.upper() not in valid: + raise ValueError(f"priority debe ser uno de: {valid}") + return v.upper() + + @field_validator("content") + @classmethod + def validate_content(cls, v: str) -> str: + if not v.strip(): + raise ValueError("content no puede estar vacío") + return v.strip() + + +class IssueResponse(BaseModel): + id: uuid.UUID + ticket_id: uuid.UUID + tenant_id: uuid.UUID + content: str + priority: str + created_by: uuid.UUID + created_by_name: str + tagged_users: List[TaggedUserBasic] = [] + attachment_filename: Optional[str] = None + attachment_mime_type: Optional[str] = None + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/tickets.py b/backend/app/api/v1/endpoints/tickets.py index 193460d..4de935e 100644 --- a/backend/app/api/v1/endpoints/tickets.py +++ b/backend/app/api/v1/endpoints/tickets.py @@ -7,6 +7,8 @@ from sqlalchemy.orm import selectinload from typing import List, Optional from datetime import datetime, timedelta import uuid +from app.models.issue import TicketIssue, ticket_issue_tagged_users +from app.api.schemas.issue import IssueCreate, IssueResponse from app.core.database import get_db from app.api.deps import get_current_user, get_current_tenant @@ -411,3 +413,179 @@ async def download_attachment(ticket_id: str, attachment_id: str, db: AsyncSessi logger.info(f"Returning file: {attachment.original_filename}") return FileResponse(path=file_path, filename=attachment.original_filename, media_type=attachment.mime_type) + +@router.get("/{ticket_id}/issues", response_model=List[IssueResponse]) +async def get_ticket_issues( + ticket_id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): + """Obtener asuntos de un ticket.""" + ticket_uuid = validate_uuid_param(ticket_id, "ticket ID") + + # Verificar que el ticket existe y pertenece al tenant + query = select(Ticket).where( + Ticket.id == ticket_uuid, + Ticket.tenant_id == current_user.tenant_id, + ) + if current_user.role.is_client: + query = query.where(Ticket.created_by == current_user.id) + + result = await db.execute(query) + ticket_obj = result.scalars().first() + if not ticket_obj: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ticket no encontrado") + + issues_query = ( + select(TicketIssue) + .where(TicketIssue.ticket_id == ticket_uuid) + .options( + selectinload(TicketIssue.created_by_user), + selectinload(TicketIssue.tagged_users), + ) + .order_by(TicketIssue.created_at.desc()) + ) + result = await db.execute(issues_query) + issues = result.scalars().all() + + return [ + IssueResponse( + id=issue.id, + ticket_id=issue.ticket_id, + tenant_id=issue.tenant_id, + content=issue.content, + priority=issue.priority.value, + created_by=issue.created_by, + created_by_name=f"{issue.created_by_user.first_name} {issue.created_by_user.last_name}", + tagged_users=[ + {"id": u.id, "full_name": f"{u.first_name} {u.last_name}", "email": u.email} + for u in issue.tagged_users + ], + attachment_filename=issue.attachment_filename, + attachment_mime_type=issue.attachment_mime_type, + created_at=issue.created_at, + updated_at=issue.updated_at, + ) + for issue in issues + ] + + +@router.post("/{ticket_id}/issues", response_model=IssueResponse, status_code=status.HTTP_201_CREATED) +async def create_ticket_issue( + ticket_id: str, + issue: IssueCreate, + file: Optional[UploadFile] = File(None), + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), + current_tenant: Tenant = Depends(get_current_tenant), +): + """ + Crear un asunto de escalación en un ticket. + + Solo puede crearlo el dueño del ticket o el CLIENT_ADMIN del tenant. + """ + ticket_uuid = validate_uuid_param(ticket_id, "ticket ID") + + # 1. Verificar que el ticket existe y pertenece al tenant + result = await db.execute( + select(Ticket).where( + Ticket.id == ticket_uuid, + Ticket.tenant_id == current_user.tenant_id, + ) + ) + ticket_obj = result.scalars().first() + if not ticket_obj: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ticket no encontrado") + + # 2. Verificar permisos: solo el creador del ticket o CLIENT_ADMIN + is_ticket_owner = ticket_obj.created_by == current_user.id + is_client_admin = current_user.role == UserRole.CLIENT_ADMIN + + if not is_ticket_owner and not is_client_admin: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Solo el creador del ticket o el administrador del tenant pueden crear asuntos", + ) + + # 3. Validar usuarios etiquetados — deben pertenecer al mismo tenant + tagged_users = [] + if issue.tagged_user_ids: + result = await db.execute( + select(User).where( + User.id.in_(issue.tagged_user_ids), + User.tenant_id == current_user.tenant_id, + User.is_active == True, + ) + ) + tagged_users = result.scalars().all() + + found_ids = {u.id for u in tagged_users} + missing = [str(uid) for uid in issue.tagged_user_ids if uid not in found_ids] + if missing: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Usuarios no encontrados en el tenant: {missing}", + ) + + # 4. Manejar adjunto opcional + attachment_path = None + attachment_filename = None + attachment_mime_type = None + if file and file.filename: + file_metadata = await file_handler.save_upload(file, current_tenant.id, ticket_uuid) + attachment_path = file_metadata["file_path"] + attachment_filename = file_metadata["original_filename"] + attachment_mime_type = file_metadata["mime_type"] + + # 5. Crear el asunto + new_issue = TicketIssue( + id=uuid.uuid4(), + ticket_id=ticket_uuid, + tenant_id=current_user.tenant_id, + created_by=current_user.id, + content=issue.content, + priority=TicketPriority[issue.priority], + attachment_path=attachment_path, + attachment_filename=attachment_filename, + attachment_mime_type=attachment_mime_type, + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + ) + new_issue.tagged_users = tagged_users + db.add(new_issue) + + await db.commit() + await db.refresh(new_issue, ["created_by_user", "tagged_users"]) + + # 6. Audit log + await safe_audit_log( + db=db, + tenant_id=current_user.tenant_id, + user_id=current_user.id, + action="ticket.issue.create", + resource_type="ticket_issue", + resource_id=new_issue.id, + new_values={ + "ticket_id": str(ticket_uuid), + "priority": issue.priority, + "tagged_users": [str(uid) for uid in issue.tagged_user_ids], + }, + ) + + return IssueResponse( + id=new_issue.id, + ticket_id=new_issue.ticket_id, + tenant_id=new_issue.tenant_id, + content=new_issue.content, + priority=new_issue.priority.value, + created_by=new_issue.created_by, + created_by_name=f"{new_issue.created_by_user.first_name} {new_issue.created_by_user.last_name}", + tagged_users=[ + {"id": u.id, "full_name": f"{u.first_name} {u.last_name}", "email": u.email} + for u in new_issue.tagged_users + ], + attachment_filename=new_issue.attachment_filename, + attachment_mime_type=new_issue.attachment_mime_type, + created_at=new_issue.created_at, + updated_at=new_issue.updated_at, + ) \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index f11cae0..236785f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -35,6 +35,7 @@ from app.middleware.tenant import TenantMiddleware from app.middleware.correlation_id import CorrelationIDMiddleware from app.core.cache import cache from app.core.limiter import limiter +from app.models.issue import TicketIssue, ticket_issue_tagged_users settings = get_settings() setup_logging() diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 4d14d97..02418a4 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -3,6 +3,7 @@ from .user import User from .tenant import Tenant from .ticket import Ticket +from .issue import TicketIssue, ticket_issue_tagged_users from .comment import TicketComment from .system import System from .category import Category @@ -13,8 +14,9 @@ from .refresh_token import RefreshToken __all__ = [ "User", - "Tenant", + "Tenant", "Ticket", + "TicketIssue", "TicketComment", "System", "Category", diff --git a/backend/app/models/issue.py b/backend/app/models/issue.py new file mode 100644 index 0000000..226cb77 --- /dev/null +++ b/backend/app/models/issue.py @@ -0,0 +1,102 @@ +""" +TicketIssue Model - ServiceManagerWeb + +Asuntos de escalación asociados a tickets. +Creados por el dueño del ticket o el CLIENT_ADMIN del tenant. +""" +from sqlalchemy import String, ForeignKey, Text, Table, Column +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import Optional, List +from datetime import datetime +import uuid + +from app.core.database import Base, GUID +from app.models.ticket import TicketPriority +from sqlalchemy import Enum as SAEnum +from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM + + +# Tabla de relación N:M entre TicketIssue y User (usuarios etiquetados) +ticket_issue_tagged_users = Table( + "ticket_issue_tagged_users", + Base.metadata, + Column( + "issue_id", + GUID(), + ForeignKey("ticket_issues.id", ondelete="CASCADE"), + primary_key=True, + nullable=False, + ), + Column( + "user_id", + GUID(), + ForeignKey("users.id", ondelete="CASCADE"), + primary_key=True, + nullable=False, + ), +) + + +class TicketIssue(Base): + """ + Asunto de escalación de un ticket. + + Solo puede crearlo: + - El usuario que creó el ticket (created_by del Ticket) + - El CLIENT_ADMIN del mismo tenant + """ + __tablename__ = "ticket_issues" + + # Multi-tenancy — siempre filtrar por esto + tenant_id: Mapped[uuid.UUID] = mapped_column( + GUID(), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False, + ) + + # Relación con el ticket padre + ticket_id: Mapped[uuid.UUID] = mapped_column( + GUID(), + ForeignKey("tickets.id", ondelete="CASCADE"), + nullable=False, + ) + + # Quién lo creó + created_by: Mapped[uuid.UUID] = mapped_column( + GUID(), + ForeignKey("users.id"), + nullable=False, + ) + + # Contenido + content: Mapped[str] = mapped_column(Text, nullable=False) + + priority: Mapped[TicketPriority] = mapped_column( + SAEnum(TicketPriority, name="ticket_priority_enum", native_enum=False).with_variant( + PG_ENUM(TicketPriority, name="ticket_priority_enum", create_type=True), + "postgresql", + ), + default=TicketPriority.MEDIUM, + nullable=False, + ) + + # Adjunto opcional (reutiliza file_handler igual que TicketAttachment) + attachment_path: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) + attachment_filename: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + attachment_mime_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) + + # Relationships + ticket: Mapped["Ticket"] = relationship("Ticket", back_populates="issues") + + created_by_user: Mapped["User"] = relationship( + "User", + foreign_keys=[created_by], + ) + + tagged_users: Mapped[List["User"]] = relationship( + "User", + secondary=ticket_issue_tagged_users, + ) + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/backend/app/models/ticket.py b/backend/app/models/ticket.py index 0ae54c2..58a8511 100644 --- a/backend/app/models/ticket.py +++ b/backend/app/models/ticket.py @@ -163,6 +163,12 @@ class Ticket(Base): cascade="all, delete-orphan" ) + issues: Mapped[list["TicketIssue"]] = relationship( + "TicketIssue", + back_populates="ticket", + cascade="all, delete-orphan" +) + def __repr__(self) -> str: return f"" diff --git a/backend/migrations/versions/b2dcb926e091_add_ticket_issues.py b/backend/migrations/versions/b2dcb926e091_add_ticket_issues.py new file mode 100644 index 0000000..63cdd1b --- /dev/null +++ b/backend/migrations/versions/b2dcb926e091_add_ticket_issues.py @@ -0,0 +1,63 @@ +"""add_ticket_issues + +Revision ID: b2dcb926e091 +Revises: 46bccd948688 +Create Date: 2026-03-17 16:25:34.452826 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision = 'b2dcb926e091' +down_revision = '46bccd948688' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + conn = op.get_bind() + inspector = sa.inspect(conn) + existing_tables = inspector.get_table_names() + + if 'ticket_issues' not in existing_tables: + op.create_table( + 'ticket_issues', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('tenant_id', sa.UUID(), nullable=False), + sa.Column('ticket_id', sa.UUID(), nullable=False), + sa.Column('created_by', sa.UUID(), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column( + 'priority', + postgresql.ENUM('LOW', 'MEDIUM', 'HIGH', 'URGENT', + name='ticket_priority_enum', create_type=False), + nullable=False, + ), + sa.Column('attachment_path', sa.String(500), nullable=True), + sa.Column('attachment_filename', sa.String(255), nullable=True), + sa.Column('attachment_mime_type', sa.String(100), nullable=True), + sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), + server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), + server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['ticket_id'], ['tickets.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['created_by'], ['users.id']), + sa.PrimaryKeyConstraint('id'), + ) + + if 'ticket_issue_tagged_users' not in existing_tables: + op.create_table( + 'ticket_issue_tagged_users', + sa.Column('issue_id', sa.UUID(), nullable=False), + sa.Column('user_id', sa.UUID(), nullable=False), + sa.ForeignKeyConstraint(['issue_id'], ['ticket_issues.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('issue_id', 'user_id'), + ) + + +def downgrade() -> None: + op.drop_table('ticket_issue_tagged_users') + op.drop_table('ticket_issues') \ No newline at end of file diff --git a/check_schema.py b/check_schema.py deleted file mode 100644 index c638b34..0000000 --- a/check_schema.py +++ /dev/null @@ -1,3 +0,0 @@ -with open("/app/app/api/schemas/category.py", "r") as f: - content = f.read() -print(content) diff --git a/debug_tenant.py b/debug_tenant.py deleted file mode 100644 index 801923a..0000000 --- a/debug_tenant.py +++ /dev/null @@ -1,19 +0,0 @@ -import asyncio -from app.core.database import AsyncSessionLocal -from app.models.user import User -from app.models.tenant import Tenant -from sqlalchemy import select - -async def check(): - async with AsyncSessionLocal() as db: - result = await db.execute( - select(User, Tenant).join(Tenant).where(User.email == 'javier@ventas.com') - ) - user, tenant = result.one() - print(f"user.tenant_id: {user.tenant_id}") - print(f"tenant.id: {tenant.id}") - print(f"tenant.slug: {tenant.slug}") - print(f"user.role: {user.role}") - print(f"role.is_client: {user.role.is_client}") - -asyncio.run(check()) diff --git a/fix_categories.py b/fix_categories.py deleted file mode 100644 index c45ad8a..0000000 --- a/fix_categories.py +++ /dev/null @@ -1,28 +0,0 @@ -with open("/app/app/api/v1/endpoints/categories.py", "r") as f: - content = f.read() - -old = ''' # ✅ CORREGIDO: Asignar tenant_id del usuario actual - db_category = Category( - **category.model_dump(), - tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático - )''' - -new = ''' # ADMIN global puede especificar tenant_id; otros usan el suyo - from app.models.user import UserRole as _R - data = category.model_dump() - if current_user.role == _R.ADMIN and data.get("tenant_id"): - target_tenant_id = data["tenant_id"] - else: - target_tenant_id = current_user.tenant_id - data["tenant_id"] = target_tenant_id - - db_category = Category(**data)''' - -if old in content: - content = content.replace(old, new) - print("OK: fix aplicado") -else: - print("ERROR: bloque no encontrado") - -with open("/app/app/api/v1/endpoints/categories.py", "w") as f: - f.write(content) diff --git a/fix_delete.py b/fix_delete.py deleted file mode 100644 index 9ffb5cb..0000000 --- a/fix_delete.py +++ /dev/null @@ -1,15 +0,0 @@ -with open("/app/app/api/v1/endpoints/users.py", "r") as f: - content = f.read() - -old = ' # Verificar permisos - solo ADMIN puede eliminar\n if current_user.role != UserRole.ADMIN:\n raise HTTPException(\n status_code=status.HTTP_403_FORBIDDEN,\n detail="Only admins can delete users"\n )' - -new = ' # Verificar permisos - ADMIN global o CLIENT_ADMIN pueden eliminar\n allowed = [UserRole.ADMIN, UserRole.CLIENT_ADMIN]\n if current_user.role not in allowed:\n raise HTTPException(\n status_code=status.HTTP_403_FORBIDDEN,\n detail="Only admins can delete users"\n )' - -if old in content: - content = content.replace(old, new) - print("OK: permisos delete actualizados") -else: - print("ERROR: bloque no encontrado") - -with open("/app/app/api/v1/endpoints/users.py", "w") as f: - f.write(content) diff --git a/fix_delete2.py b/fix_delete2.py deleted file mode 100644 index 7e4bfe4..0000000 --- a/fix_delete2.py +++ /dev/null @@ -1,14 +0,0 @@ -with open("/app/app/api/v1/endpoints/users.py", "r") as f: - content = f.read() - -old = " # Soft delete\n db_user.is_active = False\n await db.commit()" -new = " # Hard delete\n await db.delete(db_user)\n await db.commit()" - -if old in content: - content = content.replace(old, new) - print("OK: hard delete aplicado") -else: - print("ERROR: bloque no encontrado") - -with open("/app/app/api/v1/endpoints/users.py", "w") as f: - f.write(content) diff --git a/fix_router.py b/fix_router.py deleted file mode 100644 index 8e247fa..0000000 --- a/fix_router.py +++ /dev/null @@ -1,34 +0,0 @@ -with open("/app/app/api/v1/router.py", "r") as f: - content = f.read() - -old = "from app.api.v1.endpoints import auth, health, tenants, users, systems, categories, tickets, client_profile, audit, sla, reports" -new = "from app.api.v1.endpoints import auth, health, tenants, users, systems, categories, tickets, client_profile, audit, sla, reports, participants" - -old2 = "# Tickets routes\napi_router.include_router(\n tickets.router,\n prefix=\"/tickets\",\n tags=[\"tickets\"]\n)" -new2 = """# Tickets routes -api_router.include_router( - tickets.router, - prefix="/tickets", - tags=["tickets"] -) -# Participants routes (nested under tickets) -api_router.include_router( - participants.router, - prefix="/tickets", - tags=["participants"] -)""" - -if old in content: - content = content.replace(old, new) - print("OK: import agregado") -else: - print("ERROR: import no encontrado") - -if old2 in content: - content = content.replace(old2, new2) - print("OK: router agregado") -else: - print("ERROR: router no encontrado") - -with open("/app/app/api/v1/router.py", "w") as f: - f.write(content) diff --git a/fix_schema.py b/fix_schema.py deleted file mode 100644 index 711afe6..0000000 --- a/fix_schema.py +++ /dev/null @@ -1,30 +0,0 @@ -with open("/app/app/api/schemas/category.py", "r") as f: - content = f.read() - -old = '''class CategoryCreate(BaseModel): - """Schema para crear categoría. No incluye tenant_id (se asigna automáticamente).""" - name: str - description: Optional[str] = None - color: Optional[str] = None - sla_response_hours: int = 24 - sla_resolution_hours: int = 72 - auto_assign_to: Optional[uuid.UUID] = None''' - -new = '''class CategoryCreate(BaseModel): - """Schema para crear categoría. tenant_id opcional para ADMIN global.""" - name: str - description: Optional[str] = None - color: Optional[str] = None - sla_response_hours: int = 24 - sla_resolution_hours: int = 72 - auto_assign_to: Optional[uuid.UUID] = None - tenant_id: Optional[uuid.UUID] = None''' - -if old in content: - content = content.replace(old, new) - print("OK") -else: - print("ERROR: bloque no encontrado") - -with open("/app/app/api/schemas/category.py", "w") as f: - f.write(content) diff --git a/fix_usuarios.py b/fix_usuarios.py deleted file mode 100644 index 22b0f4c..0000000 --- a/fix_usuarios.py +++ /dev/null @@ -1,11 +0,0 @@ -with open("/app/src/routes/usuarios/+page.svelte", "r") as f: - content = f.read() - -content = content.replace( - "{#if (user as any).can_manage_users && user.role !== 'CLIENT_ADMIN'}", - "{#if user.can_manage_users && user.role !== 'CLIENT_ADMIN'}" -) - -with open("/app/src/routes/usuarios/+page.svelte", "w") as f: - f.write(content) -print("Listo")