diff --git a/backend/app/api/v1/endpoints/categories.py b/backend/app/api/v1/endpoints/categories.py index 4aaabd4..17eeaa7 100644 --- a/backend/app/api/v1/endpoints/categories.py +++ b/backend/app/api/v1/endpoints/categories.py @@ -3,53 +3,191 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel, ConfigDict from typing import List, Optional +from datetime import datetime import uuid from app.core.database import get_db from app.models.category import Category +from app.models.user import User from app.api import deps router = APIRouter() -class CategoryBase(BaseModel): +# =================================== +# PYDANTIC SCHEMAS +# =================================== + +class CategoryCreate(BaseModel): + """Schema para crear categoría - NO incluye tenant_id (se asigna automáticamente)""" name: str description: Optional[str] = None - is_active: bool = True - tenant_id: Optional[uuid.UUID] = None + color: Optional[str] = None + sla_response_hours: int = 24 + sla_resolution_hours: int = 72 + auto_assign_to: Optional[uuid.UUID] = None -class CategoryCreate(CategoryBase): - pass - -class CategoryUpdate(CategoryBase): +class CategoryUpdate(BaseModel): + """Schema para actualizar categoría""" name: Optional[str] = None description: Optional[str] = None + color: Optional[str] = None + sla_response_hours: Optional[int] = None + sla_resolution_hours: Optional[int] = None + auto_assign_to: Optional[uuid.UUID] = None is_active: Optional[bool] = None - tenant_id: Optional[uuid.UUID] = None -class CategoryResponse(CategoryBase): +class CategoryResponse(BaseModel): + """Schema de respuesta - incluye todos los campos""" id: uuid.UUID + tenant_id: uuid.UUID # ✅ AÑADIDO + name: str + description: Optional[str] = None + color: Optional[str] = None + sla_response_hours: int + sla_resolution_hours: int + auto_assign_to: Optional[uuid.UUID] = None + is_active: bool + created_at: datetime # ✅ AÑADIDO + updated_at: datetime # ✅ AÑADIDO model_config = ConfigDict(from_attributes=True) + +# =================================== +# ENDPOINTS +# =================================== + @router.get("/", response_model=List[CategoryResponse]) async def read_categories( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint + no solo superuser ): - query = select(Category).offset(skip).limit(limit) + """ + Listar categorías del tenant del usuario actual. + + ✅ Implementa multi-tenancy: solo muestra categorías del tenant del usuario. + """ + # ✅ CORREGIDO: Filtrar por tenant_id + query = select(Category).where( + Category.tenant_id == current_user.tenant_id + ).offset(skip).limit(limit) + result = await db.execute(query) return result.scalars().all() -@router.post("/", response_model=CategoryResponse) + +@router.post("/", response_model=CategoryResponse, status_code=status.HTTP_201_CREATED) async def create_category( category: CategoryCreate, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint ): - db_category = Category(**category.model_dump()) + """ + Crear nueva categoría en el tenant del usuario actual. + + ✅ Implementa multi-tenancy: asigna automáticamente tenant_id del usuario. + """ + # ✅ CORREGIDO: Asignar tenant_id del usuario actual + db_category = Category( + **category.model_dump(), + tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático + ) + db.add(db_category) await db.commit() await db.refresh(db_category) return db_category + + +@router.get("/{category_id}", response_model=CategoryResponse) +async def read_category( + category_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Obtener una categoría específica del tenant. + + ✅ Implementa multi-tenancy: solo permite acceso a categorías del propio tenant. + """ + query = select(Category).where( + Category.id == category_id, + Category.tenant_id == current_user.tenant_id # ✅ Seguridad multi-tenant + ) + result = await db.execute(query) + category = result.scalar_one_or_none() + + if not category: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Category not found" + ) + + return category + + +@router.put("/{category_id}", response_model=CategoryResponse) +async def update_category( + category_id: uuid.UUID, + category_update: CategoryUpdate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Actualizar categoría del tenant. + + ✅ Implementa multi-tenancy: solo permite actualizar categorías del propio tenant. + """ + query = select(Category).where( + Category.id == category_id, + Category.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_category = result.scalar_one_or_none() + + if not db_category: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Category not found" + ) + + # Actualizar campos + update_data = category_update.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(db_category, field, value) + + await db.commit() + await db.refresh(db_category) + return db_category + + +@router.delete("/{category_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_category( + category_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Desactivar categoría del tenant (soft delete). + + ✅ Implementa multi-tenancy: solo permite desactivar categorías del propio tenant. + """ + query = select(Category).where( + Category.id == category_id, + Category.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_category = result.scalar_one_or_none() + + if not db_category: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Category not found" + ) + + # Soft delete + db_category.is_active = False + await db.commit() + return None \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/systems.py b/backend/app/api/v1/endpoints/systems.py index a5aad98..bfc8e85 100644 --- a/backend/app/api/v1/endpoints/systems.py +++ b/backend/app/api/v1/endpoints/systems.py @@ -3,51 +3,179 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel, ConfigDict from typing import List, Optional +from datetime import datetime import uuid from app.core.database import get_db from app.models.system import System +from app.models.user import User from app.api import deps router = APIRouter() -class SystemBase(BaseModel): +# =================================== +# PYDANTIC SCHEMAS +# =================================== + +class SystemCreate(BaseModel): + """Schema para crear sistema - NO incluye tenant_id (se asigna automáticamente)""" name: str description: Optional[str] = None - is_active: bool = True -class SystemCreate(SystemBase): - pass - -class SystemUpdate(SystemBase): +class SystemUpdate(BaseModel): + """Schema para actualizar sistema""" name: Optional[str] = None description: Optional[str] = None is_active: Optional[bool] = None -class SystemResponse(SystemBase): +class SystemResponse(BaseModel): + """Schema de respuesta - incluye todos los campos""" id: uuid.UUID + tenant_id: uuid.UUID # ✅ AÑADIDO + name: str + description: Optional[str] = None + is_active: bool + created_at: datetime # ✅ AÑADIDO + updated_at: datetime # ✅ AÑADIDO model_config = ConfigDict(from_attributes=True) + +# =================================== +# ENDPOINTS +# =================================== + @router.get("/", response_model=List[SystemResponse]) async def read_systems( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint + no solo superuser ): - query = select(System).offset(skip).limit(limit) + """ + Listar sistemas del tenant del usuario actual. + + ✅ Implementa multi-tenancy: solo muestra sistemas del tenant del usuario. + """ + # ✅ CORREGIDO: Filtrar por tenant_id + query = select(System).where( + System.tenant_id == current_user.tenant_id + ).offset(skip).limit(limit) + result = await db.execute(query) return result.scalars().all() -@router.post("/", response_model=SystemResponse) + +@router.post("/", response_model=SystemResponse, status_code=status.HTTP_201_CREATED) async def create_system( system: SystemCreate, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) # ✅ CORREGIDO: Type hint ): - db_system = System(**system.model_dump()) + """ + Crear nuevo sistema en el tenant del usuario actual. + + ✅ Implementa multi-tenancy: asigna automáticamente tenant_id del usuario. + """ + # ✅ CORREGIDO: Asignar tenant_id del usuario actual + db_system = System( + **system.model_dump(), + tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático + ) + db.add(db_system) await db.commit() await db.refresh(db_system) return db_system + + +@router.get("/{system_id}", response_model=SystemResponse) +async def read_system( + system_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Obtener un sistema específico del tenant. + + ✅ Implementa multi-tenancy: solo permite acceso a sistemas del propio tenant. + """ + query = select(System).where( + System.id == system_id, + System.tenant_id == current_user.tenant_id # ✅ Seguridad multi-tenant + ) + result = await db.execute(query) + system = result.scalar_one_or_none() + + if not system: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="System not found" + ) + + return system + + +@router.put("/{system_id}", response_model=SystemResponse) +async def update_system( + system_id: uuid.UUID, + system_update: SystemUpdate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Actualizar sistema del tenant. + + ✅ Implementa multi-tenancy: solo permite actualizar sistemas del propio tenant. + """ + query = select(System).where( + System.id == system_id, + System.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_system = result.scalar_one_or_none() + + if not db_system: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="System not found" + ) + + # Actualizar campos + update_data = system_update.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(db_system, field, value) + + await db.commit() + await db.refresh(db_system) + return db_system + + +@router.delete("/{system_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_system( + system_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Desactivar sistema del tenant (soft delete). + + ✅ Implementa multi-tenancy: solo permite desactivar sistemas del propio tenant. + """ + query = select(System).where( + System.id == system_id, + System.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_system = result.scalar_one_or_none() + + if not db_system: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="System not found" + ) + + # Soft delete + db_system.is_active = False + await db.commit() + return None \ No newline at end of file diff --git a/backend/app/api/v1/endpoints/tenants.py b/backend/app/api/v1/endpoints/tenants.py index ae4e035..15a5bab 100644 --- a/backend/app/api/v1/endpoints/tenants.py +++ b/backend/app/api/v1/endpoints/tenants.py @@ -85,6 +85,9 @@ async def update_tenant( raise HTTPException(status_code=404, detail="Tenant not found") update_data = tenant_in.model_dump(exclude_unset=True) + if "status" in update_data: + tenant.is_active = update_data.pop("status") == TenantStatus.active + for field, value in update_data.items(): setattr(tenant, field, value) @@ -92,3 +95,18 @@ async def update_tenant( await db.commit() await db.refresh(tenant) return tenant + +@router.delete("/{tenant_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_tenant( + tenant_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user = Depends(deps.get_current_active_superuser) +): + """Eliminar un cliente (tenant) por ID.""" + tenant = await db.get(Tenant, tenant_id) + if not tenant: + raise HTTPException(status_code=404, detail="Tenant not found") + + await db.delete(tenant) + await db.commit() + return {"message": "Tenant deleted successfully"} diff --git a/backend/app/api/v1/endpoints/tickets.py b/backend/app/api/v1/endpoints/tickets.py index 020e3fc..e4a0fc3 100644 --- a/backend/app/api/v1/endpoints/tickets.py +++ b/backend/app/api/v1/endpoints/tickets.py @@ -3,6 +3,7 @@ Tickets endpoints - ServiceManagerWeb """ from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File +from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from typing import List, Optional @@ -11,7 +12,8 @@ from app.core.database import get_db from app.api.deps import get_current_user from app.models.ticket import Ticket, TicketStatus, TicketPriority from app.models.user import User -from pydantic import BaseModel +from app.models.category import Category # ✅ CORREGIDO: Era TicketCategory +from app.models.system import System import uuid router = APIRouter() @@ -24,7 +26,7 @@ class TicketCreate(BaseModel): subject: str description: str category_id: Optional[str] = None - system_id: Optional[str] = None + affected_system_id: Optional[str] = None # ✅ CORREGIDO: Era system_id priority: str = "MEDIUM" class TicketUpdate(BaseModel): @@ -42,7 +44,7 @@ class TicketResponse(BaseModel): status: str priority: str category_id: Optional[str] = None - system_id: Optional[str] = None + affected_system_id: Optional[str] = None # ✅ CORREGIDO: Era system_id created_by: str assigned_to: Optional[str] = None created_at: datetime @@ -78,7 +80,25 @@ async def create_ticket( # Convertir IDs de string a UUID si son proporcionados category_uuid = uuid.UUID(ticket.category_id) if ticket.category_id else None - system_uuid = uuid.UUID(ticket.system_id) if ticket.system_id else None + system_uuid = uuid.UUID(ticket.affected_system_id) if ticket.affected_system_id else None # ✅ CORREGIDO + + # ✅ CORREGIDO: Validar en la tabla correcta con el nombre correcto del modelo + if category_uuid: + category = await db.get(Category, category_uuid) # ✅ Category, no TicketCategory + if not category: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"La categoría con ID {ticket.category_id} no existe." + ) + + # Validar si el system_id existe en la tabla affected_systems + if system_uuid: + system = await db.get(System, system_uuid) + if not system: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"El sistema con ID {ticket.affected_system_id} no existe." + ) db_ticket = Ticket( id=uuid.uuid4(), @@ -87,7 +107,7 @@ async def create_ticket( subject=ticket.subject, description=ticket.description, category_id=category_uuid, - system_id=system_uuid, + affected_system_id=system_uuid, # ✅ CORREGIDO: Nombre correcto del campo priority=TicketPriority[ticket.priority.upper()], created_by=current_user.id, status=TicketStatus.NEW, @@ -99,7 +119,7 @@ async def create_ticket( await db.commit() await db.refresh(db_ticket) - # Convertir a respuesta + # ✅ CORREGIDO: Usar affected_system_id en respuesta return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, @@ -108,7 +128,7 @@ async def create_ticket( "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, - "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, + "affected_system_id": str(db_ticket.affected_system_id) if db_ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, @@ -160,6 +180,7 @@ async def get_tickets( result = await db.execute(query) tickets = result.scalars().all() + # ✅ CORREGIDO: Usar affected_system_id return [ { "id": str(t.id), @@ -169,7 +190,7 @@ async def get_tickets( "status": t.status.value, "priority": t.priority.value, "category_id": str(t.category_id) if t.category_id else None, - "system_id": str(t.system_id) if t.system_id else None, + "affected_system_id": str(t.affected_system_id) if t.affected_system_id else None, # ✅ CORREGIDO "created_by": str(t.created_by), "assigned_to": str(t.assigned_to) if t.assigned_to else None, "created_at": t.created_at, @@ -211,6 +232,7 @@ async def get_ticket( detail=f"Ticket {ticket_id} not found" ) + # ✅ CORREGIDO: Usar affected_system_id return { "id": str(ticket.id), "ticket_number": ticket.ticket_number, @@ -219,7 +241,7 @@ async def get_ticket( "status": ticket.status.value, "priority": ticket.priority.value, "category_id": str(ticket.category_id) if ticket.category_id else None, - "system_id": str(ticket.system_id) if ticket.system_id else None, + "affected_system_id": str(ticket.affected_system_id) if ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(ticket.created_by), "assigned_to": str(ticket.assigned_to) if ticket.assigned_to else None, "created_at": ticket.created_at, @@ -278,6 +300,7 @@ async def update_ticket( await db.commit() await db.refresh(db_ticket) + # ✅ CORREGIDO: Usar affected_system_id return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, @@ -286,7 +309,7 @@ async def update_ticket( "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, - "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, + "affected_system_id": str(db_ticket.affected_system_id) if db_ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, @@ -341,6 +364,7 @@ async def close_ticket( await db.commit() await db.refresh(db_ticket) + # ✅ CORREGIDO: Usar affected_system_id return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, @@ -349,7 +373,7 @@ async def close_ticket( "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, - "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, + "affected_system_id": str(db_ticket.affected_system_id) if db_ticket.affected_system_id else None, # ✅ CORREGIDO "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, diff --git a/backend/app/api/v1/endpoints/users.py b/backend/app/api/v1/endpoints/users.py index 82d3272..0f52932 100644 --- a/backend/app/api/v1/endpoints/users.py +++ b/backend/app/api/v1/endpoints/users.py @@ -3,6 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel, ConfigDict, EmailStr from typing import List, Optional +from datetime import datetime import uuid from app.core.database import get_db @@ -12,57 +13,335 @@ from app.api import deps router = APIRouter() -class UserBase(BaseModel): +# =================================== +# PYDANTIC SCHEMAS +# =================================== + +class UserCreate(BaseModel): + """Schema para crear usuario - NO incluye tenant_id (se asigna automáticamente)""" email: EmailStr first_name: str last_name: str role: UserRole - is_active: bool = True - tenant_id: Optional[uuid.UUID] = None - -class UserCreate(UserBase): password: str - + language: str = "es" + timezone: str = "UTC" + notifications_email: bool = True + class UserUpdate(BaseModel): + """Schema para actualizar usuario""" email: Optional[EmailStr] = None first_name: Optional[str] = None last_name: Optional[str] = None role: Optional[UserRole] = None is_active: Optional[bool] = None - password: Optional[str] = None # Optional password update + password: Optional[str] = None + language: Optional[str] = None + timezone: Optional[str] = None + notifications_email: Optional[bool] = None -class UserResponse(UserBase): +class UserResponse(BaseModel): + """Schema de respuesta - incluye todos los campos públicos""" id: uuid.UUID + tenant_id: uuid.UUID + email: EmailStr + first_name: str + last_name: str + avatar_url: Optional[str] = None + role: UserRole + is_active: bool + email_verified: bool + last_login: Optional[datetime] = None + language: str + timezone: str + notifications_email: bool + totp_enabled: bool + created_at: datetime + updated_at: datetime model_config = ConfigDict(from_attributes=True) + +# =================================== +# ENDPOINTS +# =================================== + @router.get("/", response_model=List[UserResponse]) async def read_users( skip: int = 0, - limit: int = 100, + limit: int = 100, + role: Optional[UserRole] = None, + is_active: Optional[bool] = None, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) ): - query = select(User).offset(skip).limit(limit) + """ + Listar usuarios del tenant del usuario actual. + + ✅ Implementa multi-tenancy: solo muestra usuarios del tenant del usuario. + + Filtros opcionales: + - role: filtrar por rol + - is_active: filtrar por estado activo + """ + # ✅ CORREGIDO: Filtrar por tenant_id + query = select(User).where(User.tenant_id == current_user.tenant_id) + + # Aplicar filtros opcionales + if role: + query = query.where(User.role == role) + if is_active is not None: + query = query.where(User.is_active == is_active) + + query = query.offset(skip).limit(limit).order_by(User.created_at.desc()) + result = await db.execute(query) return result.scalars().all() -@router.post("/", response_model=UserResponse) + +@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def create_user( user: UserCreate, db: AsyncSession = Depends(get_db), - current_user = Depends(deps.get_current_active_superuser) + current_user: User = Depends(deps.get_current_user) ): - query = select(User).where(User.email == user.email) + """ + Crear nuevo usuario en el tenant del usuario actual. + + ✅ Implementa multi-tenancy: asigna automáticamente tenant_id del usuario. + + Restricciones: + - Solo ADMIN, SUPPORT_MANAGER y CLIENT_ADMIN pueden crear usuarios + - El email debe ser único dentro del tenant + """ + # Verificar permisos + if not current_user.can_manage_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have permission to create users" + ) + + # Verificar si el email ya existe en el tenant + query = select(User).where( + User.email == user.email, + User.tenant_id == current_user.tenant_id + ) result = await db.execute(query) if result.scalar_one_or_none(): - raise HTTPException(status_code=400, detail="Email already registered") - - user_data = user.model_dump(exclude={"password"}) - password_hash = security.get_password_hash(user.password) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already registered in this tenant" + ) + + # Preparar datos del usuario + user_data = user.model_dump(exclude={"password"}) + password_hash = security.hash_password(user.password) + + # ✅ CORREGIDO: Asignar tenant_id del usuario actual + db_user = User( + **user_data, + password_hash=password_hash, + tenant_id=current_user.tenant_id # ✅ Multi-tenancy automático + ) - db_user = User(**user_data, password_hash=password_hash) db.add(db_user) await db.commit() await db.refresh(db_user) return db_user + + +@router.get("/{user_id}", response_model=UserResponse) +async def read_user( + user_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Obtener un usuario específico del tenant. + + ✅ Implementa multi-tenancy: solo permite acceso a usuarios del propio tenant. + """ + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id # ✅ Seguridad multi-tenant + ) + result = await db.execute(query) + user = result.scalar_one_or_none() + + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + return user + + +@router.put("/{user_id}", response_model=UserResponse) +async def update_user( + user_id: uuid.UUID, + user_update: UserUpdate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Actualizar usuario del tenant. + + ✅ Implementa multi-tenancy: solo permite actualizar usuarios del propio tenant. + + Restricciones: + - Solo ADMIN, SUPPORT_MANAGER y CLIENT_ADMIN pueden actualizar usuarios + - No se puede cambiar el tenant_id + """ + # Verificar permisos + if not current_user.can_manage_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have permission to update users" + ) + + # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # Verificar email único si se está cambiando + update_data = user_update.model_dump(exclude_unset=True) + if "email" in update_data and update_data["email"] != db_user.email: + email_query = select(User).where( + User.email == update_data["email"], + User.tenant_id == current_user.tenant_id, + User.id != user_id + ) + email_result = await db.execute(email_query) + if email_result.scalar_one_or_none(): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already in use by another user" + ) + + # Actualizar campos + for field, value in update_data.items(): + if field == "password": + # Hash the new password + db_user.password_hash = security.hash_password(value) + else: + setattr(db_user, field, value) + + await db.commit() + await db.refresh(db_user) + return db_user + + +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_user( + user_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Desactivar usuario del tenant (soft delete). + + ✅ Implementa multi-tenancy: solo permite desactivar usuarios del propio tenant. + + Restricciones: + - Solo ADMIN puede eliminar usuarios + - No se puede eliminar a sí mismo + - No se puede eliminar el último ADMIN del tenant + """ + # Verificar permisos - solo ADMIN puede eliminar + if current_user.role != UserRole.ADMIN: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Only admins can delete users" + ) + + # No se puede eliminar a sí mismo + if user_id == current_user.id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="You cannot delete yourself" + ) + + # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # Verificar que no sea el último admin del tenant + if db_user.role == UserRole.ADMIN: + admin_query = select(User).where( + User.tenant_id == current_user.tenant_id, + User.role == UserRole.ADMIN, + User.is_active == True, + User.id != user_id + ) + admin_result = await db.execute(admin_query) + active_admins = admin_result.scalars().all() + + if len(active_admins) == 0: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Cannot delete the last active admin of the tenant" + ) + + # Soft delete + db_user.is_active = False + await db.commit() + return None + + +@router.patch("/{user_id}/activate", response_model=UserResponse) +async def activate_user( + user_id: uuid.UUID, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user) +): + """ + Reactivar usuario desactivado. + + ✅ Implementa multi-tenancy: solo permite reactivar usuarios del propio tenant. + """ + # Verificar permisos + if not current_user.can_manage_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have permission to activate users" + ) + + # Buscar usuario + query = select(User).where( + User.id == user_id, + User.tenant_id == current_user.tenant_id + ) + result = await db.execute(query) + db_user = result.scalar_one_or_none() + + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + db_user.is_active = True + await db.commit() + await db.refresh(db_user) + return db_user diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index f53b71f..65174f1 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -5,6 +5,7 @@ Router principal para la API v1 """ from fastapi import APIRouter + from app.api.v1.endpoints import auth, health, tenants, users, systems, categories, tickets api_router = APIRouter() @@ -51,4 +52,8 @@ api_router.include_router( tickets.router, prefix="/tickets", tags=["tickets"] -) \ No newline at end of file +) + +# Ensure FastAPI is installed in the environment +# If not, install it using: +# pip install fastapi \ No newline at end of file diff --git a/backend/app/models/category.py b/backend/app/models/category.py index 8ccb9b5..64f0d44 100644 --- a/backend/app/models/category.py +++ b/backend/app/models/category.py @@ -1,8 +1,8 @@ - """ Category Model - ServiceManagerWeb +Categorías de tickets por tenant """ -from sqlalchemy import String, Text, Boolean, ForeignKey +from sqlalchemy import String, Text, Boolean, Integer, ForeignKey, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID from typing import List, Optional @@ -11,18 +11,40 @@ import uuid from app.core.database import Base class Category(Base): - __tablename__ = "categories" + """Modelo de categorías de tickets (ticket_categories en BD)""" + __tablename__ = "ticket_categories" # ✅ CORREGIDO: nombre correcto de tabla + # Campos básicos name: Mapped[str] = mapped_column(String(100), nullable=False) - description: Mapped[Optional[str]] = mapped_column(Text) - is_active: Mapped[bool] = mapped_column(Boolean, default=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - # Optional: Tenant specific categories? - tenant_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=True) + # ✅ CORREGIDO: tenant_id es obligatorio para multi-tenancy + tenant_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False # ✅ Obligatorio + ) + + # ✅ AÑADIDOS: Campos de SLA según schema.sql + color: Mapped[Optional[str]] = mapped_column(String(7), nullable=True) + sla_response_hours: Mapped[int] = mapped_column(Integer, default=24, nullable=False) + sla_resolution_hours: Mapped[int] = mapped_column(Integer, default=72, nullable=False) + auto_assign_to: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=True + ) # Relationships tickets: Mapped[List["Ticket"]] = relationship("Ticket", back_populates="category") - tenant: Mapped["Tenant"] = relationship("Tenant") # Assuming Tenant model is imported + tenant: Mapped["Tenant"] = relationship("Tenant") + auto_assign_user: Mapped[Optional["User"]] = relationship("User", foreign_keys=[auto_assign_to]) + + # ✅ AÑADIDO: Constraint único por tenant (no puede haber categorías duplicadas en el mismo tenant) + __table_args__ = ( + UniqueConstraint('tenant_id', 'name', name='uq_ticket_categories_tenant_name'), + ) def __repr__(self) -> str: - return f"" + return f"" \ No newline at end of file diff --git a/backend/app/models/relationships.py b/backend/app/models/relationships.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/system.py b/backend/app/models/system.py index 70929d1..a29c43c 100644 --- a/backend/app/models/system.py +++ b/backend/app/models/system.py @@ -1,25 +1,43 @@ - """ System Model - ServiceManagerWeb +Sistemas afectados por tenant """ -from sqlalchemy import String, Text, Boolean +from sqlalchemy import String, Text, Boolean, ForeignKey, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID from typing import List, Optional import uuid from app.core.database import Base class System(Base): - __tablename__ = "systems" + """Modelo de sistemas afectados (affected_systems en BD)""" + __tablename__ = "affected_systems" # ✅ CORREGIDO: nombre correcto de tabla + # Campos básicos name: Mapped[str] = mapped_column(String(100), nullable=False) - description: Mapped[Optional[str]] = mapped_column(Text) - is_active: Mapped[bool] = mapped_column(Boolean, default=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) + + # ✅ AÑADIDO: tenant_id obligatorio para multi-tenancy (faltaba completamente) + tenant_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False + ) # Relationships - # If we want tickets to link to systems, we will add relationship in Ticket later or now. - # We will assume Ticket links to System. - tickets: Mapped[List["Ticket"]] = relationship("Ticket", back_populates="system") + # ✅ ACTUALIZADO: nombre de relación a affected_system + tickets: Mapped[List["Ticket"]] = relationship( + "Ticket", + back_populates="affected_system" # ✅ Nombre actualizado + ) + tenant: Mapped["Tenant"] = relationship("Tenant") + + # ✅ AÑADIDO: Constraint único por tenant (no puede haber sistemas duplicados en el mismo tenant) + __table_args__ = ( + UniqueConstraint('tenant_id', 'name', name='uq_affected_systems_tenant_name'), + ) def __repr__(self) -> str: - return f"" + return f"" \ No newline at end of file diff --git a/backend/app/models/tenant.py b/backend/app/models/tenant.py index e37f110..35782b4 100644 --- a/backend/app/models/tenant.py +++ b/backend/app/models/tenant.py @@ -1,9 +1,7 @@ """ Tenant Model - ServiceManagerWeb - Modelo para organizaciones cliente (multi-tenancy) """ - from sqlalchemy import String, Integer, Text, Boolean, ARRAY from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, ENUM @@ -13,17 +11,14 @@ import uuid from app.core.database import Base - class TenantStatus(str, enum.Enum): """Estados de un tenant.""" ACTIVE = "active" SUSPENDED = "suspended" INACTIVE = "inactive" - class Tenant(Base): """Modelo de Tenant (Organización cliente).""" - __tablename__ = "tenants" # Información básica @@ -51,18 +46,14 @@ class Tenant(Base): # Estado status: Mapped[TenantStatus] = mapped_column( - String(20), + String(20), default=TenantStatus.ACTIVE ) # Relaciones users: Mapped[List["User"]] = relationship("User", back_populates="tenant") tickets: Mapped[List["Ticket"]] = relationship("Ticket", back_populates="tenant") + categories: Mapped[List["Category"]] = relationship("Category", back_populates="tenant") # ✅ CORREGIDO: Era "TicketCategory" def __repr__(self) -> str: return f"" - - @property - def is_active(self) -> bool: - """Check if tenant is active.""" - return self.status == TenantStatus.ACTIVE \ No newline at end of file diff --git a/backend/app/models/ticket.py b/backend/app/models/ticket.py index a78aadb..a9ba19d 100644 --- a/backend/app/models/ticket.py +++ b/backend/app/models/ticket.py @@ -1,56 +1,112 @@ """ Ticket Model - ServiceManagerWeb +Tickets de soporte - Core del negocio """ -from sqlalchemy import String, ForeignKey, Text +from sqlalchemy import String, ForeignKey, Text, Integer, CheckConstraint, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, ENUM from typing import Optional +from datetime import datetime import enum import uuid from app.core.database import Base class TicketStatus(str, enum.Enum): + """Estados posibles de un ticket""" NEW = "NEW" TRIAGE = "TRIAGE" IN_PROGRESS = "IN_PROGRESS" - WAITING_FOR_CLIENT = "WAITING_FOR_CLIENT" + WAITING_CUSTOMER = "WAITING_CUSTOMER" # ✅ CORREGIDO: nombre según schema.sql RESOLVED = "RESOLVED" CLOSED = "CLOSED" REOPENED = "REOPENED" class TicketPriority(str, enum.Enum): + """Prioridades posibles de un ticket""" LOW = "LOW" MEDIUM = "MEDIUM" HIGH = "HIGH" URGENT = "URGENT" class Ticket(Base): + """Modelo de tickets de soporte""" __tablename__ = "tickets" - # Note: id, created_at, updated_at are inherited from Base - - tenant_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False) + # Multi-tenancy + tenant_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("tenants.id", ondelete="CASCADE"), + nullable=False + ) + # Campos básicos ticket_number: Mapped[str] = mapped_column(String(20), nullable=False) subject: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) - status: Mapped[TicketStatus] = mapped_column(ENUM(TicketStatus, name="ticket_status_enum", create_type=False), default=TicketStatus.NEW) - priority: Mapped[TicketPriority] = mapped_column(ENUM(TicketPriority, name="ticket_priority_enum", create_type=False), default=TicketPriority.MEDIUM) + # Estado y Prioridad + status: Mapped[TicketStatus] = mapped_column( + ENUM(TicketStatus, name="ticket_status_enum", create_type=False), + default=TicketStatus.NEW, + nullable=False + ) + priority: Mapped[TicketPriority] = mapped_column( + ENUM(TicketPriority, name="ticket_priority_enum", create_type=False), + default=TicketPriority.MEDIUM, + nullable=False + ) - # Foreign Keys - created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) - assigned_to: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) + # ✅ CORREGIDO: Foreign Keys apuntan a tablas correctas + created_by: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=False + ) + assigned_to: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id"), + nullable=True + ) - system_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("systems.id"), nullable=True) - category_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("categories.id"), nullable=True) + # ✅ CORREGIDO: Renombrado de system_id a affected_system_id + affected_system_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("affected_systems.id"), # ✅ Tabla correcta + nullable=True + ) + + # ✅ CORREGIDO: Foreign key a tabla correcta + category_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("ticket_categories.id"), # ✅ Tabla correcta + nullable=True + ) + + # ✅ AÑADIDOS: Campos de SLA según schema.sql + sla_response_due: Mapped[Optional[datetime]] = mapped_column(nullable=True) + sla_resolution_due: Mapped[Optional[datetime]] = mapped_column(nullable=True) + first_response_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) + resolved_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) + + # ✅ AÑADIDOS: Campos de CSAT (Customer Satisfaction) según schema.sql + rating: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + rating_comment: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + rated_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) # Relationships tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="tickets") - system: Mapped["System"] = relationship("System", back_populates="tickets") - category: Mapped["Category"] = relationship("Category", back_populates="tickets") + # ✅ ACTUALIZADO: Nombre de relación y optional + affected_system: Mapped[Optional["System"]] = relationship( + "System", + back_populates="tickets" + ) + + category: Mapped[Optional["Category"]] = relationship( + "Category", + back_populates="tickets" + ) created_by_user: Mapped["User"] = relationship( "User", @@ -63,3 +119,12 @@ class Ticket(Base): foreign_keys=[assigned_to], back_populates="assigned_tickets" ) + + # ✅ AÑADIDOS: Constraints según schema.sql + __table_args__ = ( + UniqueConstraint('tenant_id', 'ticket_number', name='uq_tickets_tenant_number'), + CheckConstraint('rating >= 1 AND rating <= 5', name='check_rating_range'), + ) + + def __repr__(self) -> str: + return f"" \ No newline at end of file diff --git a/backend/app/update_password_hashes.py b/backend/app/update_password_hashes.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/backend/migrations/README b/backend/backend/migrations/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/backend/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/backend/migrations/env.py b/backend/backend/migrations/env.py new file mode 100644 index 0000000..36112a3 --- /dev/null +++ b/backend/backend/migrations/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/backend/migrations/script.py.mako b/backend/backend/migrations/script.py.mako new file mode 100644 index 0000000..fbc4b07 --- /dev/null +++ b/backend/backend/migrations/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/backend/fix_password_script.py b/backend/fix_password_script.py index 81fd583..eb77c03 100644 --- a/backend/fix_password_script.py +++ b/backend/fix_password_script.py @@ -1,5 +1,4 @@ - -import asyncio +import asyncio import sys import os @@ -8,8 +7,10 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__))) from sqlalchemy import select from app.core.database import AsyncSessionLocal -from app.models.tenant import Tenant # Import Tenant to register it -from app.models.ticket import Ticket # Import Ticket to register it +from app.models.tenant import Tenant +from app.models.ticket import Ticket +from app.models.category import Category # ✅ AÑADIR ESTO +from app.models.system import System # ✅ AÑADIR ESTO from app.models.user import User from app.core.security import SecurityUtils diff --git a/backend/migrations/env.py b/backend/migrations/env.py new file mode 100644 index 0000000..6299cfd --- /dev/null +++ b/backend/migrations/env.py @@ -0,0 +1,68 @@ +from logging.config import fileConfig +import os +from sqlalchemy import create_engine, pool +from sqlalchemy.engine import engine_from_config +from alembic import context + +# Import Base and all models +from app.core.database import Base +from app.models import tenant # Import all models explicitly + +# Alembic Config object +config = context.config + +# Logging configuration +if config.config_file_name: + fileConfig(config.config_file_name) + +# Get DATABASE_URL and convert to synchronous +DATABASE_URL = os.getenv("DATABASE_URL") +if not DATABASE_URL: + raise RuntimeError("DATABASE_URL environment variable is not set") + +SYNC_DATABASE_URL = DATABASE_URL.replace("+asyncpg", "") + +# Metadata for autogenerate +target_metadata = Base.metadata + + +def run_migrations_offline(): + """ + Run migrations in 'offline' mode. + """ + context.configure( + url=SYNC_DATABASE_URL, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """ + Run migrations in 'online' mode. + """ + # Fetch the URL from Alembic configuration + alembic_config = config.get_section(config.config_ini_section) + alembic_config["sqlalchemy.url"] = SYNC_DATABASE_URL + + connectable = engine_from_config( + alembic_config, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backups b/backups new file mode 100644 index 0000000..748543a Binary files /dev/null and b/backups differ diff --git a/fix_admin_password.py b/fix_admin_password.py new file mode 100644 index 0000000..8d71848 --- /dev/null +++ b/fix_admin_password.py @@ -0,0 +1,67 @@ +""" +Script para actualizar el password del usuario admin +Ejecutar: python fix_admin_password.py +""" +import asyncio +import sys +from sqlalchemy import select, update +from passlib.context import CryptContext + +# Importar desde el proyecto +sys.path.insert(0, '/app') +from app.core.database import AsyncSessionLocal +from app.models.user import User + +# Configurar passlib igual que en security.py +pwd_context = CryptContext( + schemes=["argon2", "bcrypt"], + deprecated="auto", + argon2__memory_cost=65536, + argon2__time_cost=3, + argon2__parallelism=4, +) + +async def fix_admin_password(): + """Actualizar password del admin a 'admin123'""" + + # Generar hash del password + new_password = "admin123" + password_hash = pwd_context.hash(new_password) + + print(f"Nuevo hash generado para password: {new_password}") + print(f"Hash: {password_hash[:50]}...") + + async with AsyncSessionLocal() as session: + try: + # Buscar usuario admin + result = await session.execute( + select(User).where(User.email == "admin@aduanasoft.com") + ) + user = result.scalar_one_or_none() + + if not user: + print("❌ Usuario admin no encontrado") + return + + print(f"✅ Usuario encontrado: {user.email} (ID: {user.id})") + + # Actualizar password + user.password_hash = password_hash + + await session.commit() + + print("✅ Password actualizado exitosamente") + print(f" Email: admin@aduanasoft.com") + print(f" Password: admin123") + + except Exception as e: + await session.rollback() + print(f"❌ Error: {e}") + raise + +if __name__ == "__main__": + print("=" * 60) + print("ACTUALIZAR PASSWORD DEL ADMIN") + print("=" * 60) + asyncio.run(fix_admin_password()) + print("=" * 60) \ No newline at end of file diff --git a/frontend-client/package-lock.json b/frontend-client/package-lock.json new file mode 100644 index 0000000..6efe65a --- /dev/null +++ b/frontend-client/package-lock.json @@ -0,0 +1,3946 @@ +{ + "name": "@servicemanager/client-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@servicemanager/client-frontend", + "version": "0.1.0", + "dependencies": { + "@heroicons/react": "^2.0.18", + "@tailwindcss/forms": "^0.5.4", + "@tailwindcss/typography": "^0.5.9", + "date-fns": "^2.30.0", + "heroicons": "^2.0.18", + "zod": "^3.22.2" + }, + "devDependencies": { + "@sveltejs/adapter-node": "^1.3.1", + "@sveltejs/kit": "^1.20.4", + "@types/cookie": "^0.5.1", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "autoprefixer": "^10.4.14", + "eslint": "^8.28.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-svelte": "^2.30.0", + "postcss": "^8.4.24", + "prettier": "^2.8.0", + "prettier-plugin-svelte": "^2.10.1", + "svelte": "^4.0.5", + "svelte-check": "^3.4.3", + "tailwindcss": "^3.3.0", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "vite": "^4.4.2", + "vitest": "^0.34.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/adapter-node": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "rollup": "^3.7.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^1.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "1.30.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte": "^2.5.0", + "@types/cookie": "^0.5.1", + "cookie": "^0.5.0", + "devalue": "^4.3.1", + "esm-env": "^1.0.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.0", + "mrmime": "^1.0.1", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^2.0.2", + "tiny-glob": "^0.2.9", + "undici": "^5.28.3" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": "^16.14 || >=18" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "2.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.3", + "svelte-hmr": "^0.15.3", + "vitefu": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", + "vite": "^4.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^14.18.0 || >= 16" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.2.0", + "svelte": "^3.54.0 || ^4.0.0", + "vite": "^4.0.0" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.11", + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai-subset": { + "version": "1.3.6", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/chai": "<5.2.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.5.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/pug": { + "version": "2.0.10", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "4.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/code-red": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/code-red/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "4.3.3", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "license": "Apache-2.0" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "dev": true, + "license": "ISC" + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.18.20", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.2", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.46.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.35.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.1.0", + "semver": "^7.6.2", + "svelte-eslint-parser": "^0.43.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/heroicons": { + "version": "2.2.0", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.35.0", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "2.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/periscopic": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/periscopic/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/periscopic/node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "2.10.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^1.16.4 || ^2.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "3.29.5", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sander": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^3.1.2", + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" + } + }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sander/node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sirv/node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sorcery": { + "version": "0.11.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "buffer-crc32": "^1.0.0", + "minimist": "^1.2.0", + "sander": "^0.5.0" + }, + "bin": { + "sorcery": "bin/sorcery" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "4.2.20", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svelte-check": { + "version": "3.8.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "chokidar": "^3.4.1", + "picocolors": "^1.0.0", + "sade": "^1.7.4", + "svelte-preprocess": "^5.1.3", + "typescript": "^5.0.3" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "peerDependencies": { + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.43.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.39", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-hmr": { + "version": "0.15.3", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/svelte-preprocess": { + "version": "5.1.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/pug": "^2.0.6", + "detect-indent": "^6.1.0", + "magic-string": "^0.30.5", + "sorcery": "^0.11.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.10.2", + "coffeescript": "^2.5.1", + "less": "^3.11.3 || ^4.0.0", + "postcss": "^7 || ^8", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "pug": "^3.0.0", + "sass": "^1.26.8", + "stylus": "^0.55.0", + "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", + "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "coffeescript": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "postcss-load-config": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/svelte/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "6.0.1", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/vite": { + "version": "4.5.14", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/frontend-client/src/lib/components/Header.svelte b/frontend-client/src/lib/components/Header.svelte index 8b39789..dc60c02 100644 --- a/frontend-client/src/lib/components/Header.svelte +++ b/frontend-client/src/lib/components/Header.svelte @@ -59,6 +59,9 @@ diff --git a/frontend-client/src/lib/stores/tickets.ts b/frontend-client/src/lib/stores/tickets.ts index 72f8bea..7cadcfc 100644 --- a/frontend-client/src/lib/stores/tickets.ts +++ b/frontend-client/src/lib/stores/tickets.ts @@ -1,7 +1,6 @@ -import { writable } from 'svelte/store'; -import { auth } from './auth.js'; -import { get } from 'svelte/store'; +import { writable, get } from 'svelte/store'; import type { Writable } from 'svelte/store'; +import { auth } from './auth'; // Types export interface Ticket { @@ -121,13 +120,13 @@ function createTicketsStore() { // Load user's tickets loadTickets: async () => { - update(state => ({ ...state, isLoading: true, error: null })); + update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); try { const tickets = await apiCall('/tickets/'); - update(state => ({ ...state, tickets, isLoading: false })); + update((state: TicketsState) => ({ ...state, tickets, isLoading: false })); } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, isLoading: false, error: error instanceof Error ? error.message : 'Failed to load tickets' @@ -137,7 +136,7 @@ function createTicketsStore() { // Load specific ticket with details loadTicket: async (ticketId: string) => { - update(state => ({ ...state, isLoading: true, error: null })); + update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); try { const [ticket, comments, attachments] = await Promise.all([ @@ -146,7 +145,7 @@ function createTicketsStore() { apiCall(`/tickets/${ticketId}/attachments`) ]); - update(state => ({ + update((state: TicketsState) => ({ ...state, currentTicket: ticket, comments, @@ -154,7 +153,7 @@ function createTicketsStore() { isLoading: false })); } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, isLoading: false, error: error instanceof Error ? error.message : 'Failed to load ticket' @@ -164,7 +163,7 @@ function createTicketsStore() { // Create new ticket createTicket: async (ticket: CreateTicketRequest) => { - update(state => ({ ...state, isLoading: true, error: null })); + update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); try { // Mapear campos del frontend al formato del backend @@ -184,7 +183,7 @@ function createTicketsStore() { body: JSON.stringify(ticketData) }); - update(state => ({ + update((state: TicketsState) => ({ ...state, tickets: [newTicket, ...state.tickets], isLoading: false @@ -193,7 +192,7 @@ function createTicketsStore() { return newTicket; } catch (error) { console.error('Create ticket error:', error); - update(state => ({ + update((state: TicketsState) => ({ ...state, isLoading: false, error: error instanceof Error ? error.message : 'Failed to create ticket' @@ -210,14 +209,14 @@ function createTicketsStore() { body: JSON.stringify({ content }) }); - update(state => ({ + update((state: TicketsState) => ({ ...state, comments: [...state.comments, comment] })); return comment; } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, error: error instanceof Error ? error.message : 'Failed to add comment' })); @@ -247,14 +246,14 @@ function createTicketsStore() { const attachment = await response.json(); - update(state => ({ + update((state: TicketsState) => ({ ...state, attachments: [...state.attachments, attachment] })); return attachment; } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, error: error instanceof Error ? error.message : 'Failed to upload attachment' })); @@ -270,15 +269,15 @@ function createTicketsStore() { body: JSON.stringify({ resolution }) }); - update(state => ({ + update((state: TicketsState) => ({ ...state, currentTicket: state.currentTicket?.id === ticketId ? updatedTicket : state.currentTicket, - tickets: state.tickets.map(t => t.id === ticketId ? updatedTicket : t) + tickets: state.tickets.map((t: Ticket) => t.id === ticketId ? updatedTicket : t) })); return updatedTicket; } catch (error) { - update(state => ({ + update((state: TicketsState) => ({ ...state, error: error instanceof Error ? error.message : 'Failed to close ticket' })); @@ -288,12 +287,12 @@ function createTicketsStore() { // Clear error clearError: () => { - update(state => ({ ...state, error: null })); + update((state: TicketsState) => ({ ...state, error: null })); }, // Clear current ticket clearCurrentTicket: () => { - update(state => ({ + update((state: TicketsState) => ({ ...state, currentTicket: null, comments: [], diff --git a/frontend-internal/src/routes/tenants/+page.svelte b/frontend-internal/src/routes/tenants/+page.svelte index cf633d5..8185b30 100644 --- a/frontend-internal/src/routes/tenants/+page.svelte +++ b/frontend-internal/src/routes/tenants/+page.svelte @@ -43,11 +43,15 @@ async function handleSubmit() { try { if (editingTenant) { - await api.put(`/tenants/${editingTenant.id}`, formData); - toast.success('Cliente actualizado'); + // Asegurarse de enviar el campo "status" correctamente + const updatedData = { ...formData, status: formData.is_active ? 'active' : 'inactive' }; + await api.put(`/tenants/${editingTenant.id}`, updatedData); + toast.success(`Cliente ${formData.is_active ? 'activado' : 'desactivado'} correctamente`); } else { - await api.post('/tenants/', formData); - toast.success('Cliente creado'); + // Asegurarse de enviar el campo "status" al crear un cliente + const newData = { ...formData, status: formData.is_active ? 'active' : 'inactive' }; + await api.post('/tenants/', newData); + toast.success('Cliente creado correctamente'); } showModal = false; loadTenants(); @@ -152,6 +156,11 @@ + {#if editingTenant} + + {/if} diff --git a/frontend-internal/vite.config.js b/frontend-internal/vite.config.js index 7e41c3f..9854a1a 100644 --- a/frontend-internal/vite.config.js +++ b/frontend-internal/vite.config.js @@ -1,24 +1,24 @@ -import { sveltekit } from '@sveltejs/kit/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sveltekit()], - server: { - port: 3000, - host: '0.0.0.0', - proxy: { - '/api/v1': { - target: 'http://servicemanager-backend:8000', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, '') - } - } - }, - preview: { - port: 3000, - host: '0.0.0.0' - }, - build: { - target: 'esnext' - } + plugins: [sveltekit()], + server: { + port: 3000, + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://servicemanager-backend:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + preview: { + port: 3000, + host: '0.0.0.0' + }, + build: { + target: 'esnext' + } });