From e6440395eaf9542554021188ab4ea1cbb5ceeb16 Mon Sep 17 00:00:00 2001 From: icamarillo Date: Tue, 17 Feb 2026 12:43:06 -0700 Subject: [PATCH] v1.8.0: Sistema funcional con filtros optimizados y UI mejorada MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mejoras en Módulo de Tickets: - Implementado sistema de filtros funcional por estado y prioridad - Tabla compacta estilo auditoría (50% más espacio visible) - Backend actualizado: parámetros 'status' y 'priority' con validación - Interfaz más limpia con labels reducidos y 2 columnas de filtros - Eliminación de columna SLA duplicada en tabla Correcciones Backend: - Endpoint /v1/tickets/: filtros 'status' y 'priority' funcionan correctamente - Endpoint /v1/sla/violations: timezone UTC y eager loading con selectinload - Endpoint /v1/client-profile/: generación explícita de UUID - Migración fix_client_profiles_timestamps aplicada Mejoras UI Frontend: - Tabla tickets: encabezados uppercase text-xs, celdas px-3 py-2 - Toggle de estado activo/inactivo en gestión de tenants (tabla + modal) - Badges más compactos con rounded-full - Botones de acciones con separador visual y transiciones - Filtros con URLSearchParams para construcción correcta de queries Arquitectura: - SQLAlchemy: eager loading para evitar N+1 queries - Timezone handling: datetime.now(timezone.utc) para comparaciones - Svelte reactivity: keyed loops y spread operator para forzar updates - API client: endpoint con query string completo Estado del sistema: Totalmente funcional para producción MVP --- .../app/api/v1/endpoints/client_profile.py | 7 +- backend/app/api/v1/endpoints/sla.py | 43 +- backend/app/api/v1/endpoints/tickets.py | 27 +- .../app/api/v1/endpoints/tickets.py.backup | 451 ------- .../fix_client_profiles_timestamps.py | 47 + .../src/routes/profile/+page.svelte.backup | 1062 ----------------- .../src/routes/tenants/+page.svelte | 89 +- .../src/routes/tickets/+page.svelte | 152 ++- 8 files changed, 245 insertions(+), 1633 deletions(-) delete mode 100644 backend/app/api/v1/endpoints/tickets.py.backup create mode 100644 backend/migrations/versions/fix_client_profiles_timestamps.py delete mode 100644 frontend-client/src/routes/profile/+page.svelte.backup diff --git a/backend/app/api/v1/endpoints/client_profile.py b/backend/app/api/v1/endpoints/client_profile.py index 2fbcc80..d31a0e6 100644 --- a/backend/app/api/v1/endpoints/client_profile.py +++ b/backend/app/api/v1/endpoints/client_profile.py @@ -50,8 +50,11 @@ async def get_current_client_profile( profile = result.scalar_one_or_none() if not profile: - # Si no existe, crear uno vacío - profile = ClientProfile(tenant_id=current_tenant.id) + # Si no existe, crear uno vacío con valores por defecto explícitos + profile = ClientProfile( + id=uuid.uuid4(), + tenant_id=current_tenant.id + ) db.add(profile) await db.commit() await db.refresh(profile) diff --git a/backend/app/api/v1/endpoints/sla.py b/backend/app/api/v1/endpoints/sla.py index 2144db6..2176dc2 100644 --- a/backend/app/api/v1/endpoints/sla.py +++ b/backend/app/api/v1/endpoints/sla.py @@ -8,6 +8,7 @@ Solo accesible por roles staff internos from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func, and_, or_, desc, case, cast +from sqlalchemy.orm import selectinload from typing import Optional, List from datetime import datetime, timedelta, timezone import uuid @@ -359,8 +360,12 @@ async def get_sla_violations( now = datetime.now(timezone.utc) db_now = func.now() - # Base query - query = select(Ticket).where( + # Base query con carga de relaciones + query = select(Ticket).options( + selectinload(Ticket.created_by_user), + selectinload(Ticket.assigned_to_user), + selectinload(Ticket.category) + ).where( and_( Ticket.tenant_id == current_tenant.id, Ticket.status.notin_([TicketStatus.RESOLVED, TicketStatus.CLOSED]) @@ -421,23 +426,25 @@ async def get_sla_violations( # Formatear response violations = [] for ticket in tickets: + # Asegurar que los datetimes de BD sean timezone-aware + sla_response_due = ticket.sla_response_due.replace(tzinfo=timezone.utc) if ticket.sla_response_due and ticket.sla_response_due.tzinfo is None else ticket.sla_response_due + sla_resolution_due = ticket.sla_resolution_due.replace(tzinfo=timezone.utc) if ticket.sla_resolution_due and ticket.sla_resolution_due.tzinfo is None else ticket.sla_resolution_due + # Determinar tipo de violación - response_violated = ticket.first_response_at is None and ticket.sla_response_due and now > ticket.sla_response_due - resolution_violated = ticket.sla_resolution_due and now > ticket.sla_resolution_due + response_violated = ticket.first_response_at is None and sla_response_due and now > sla_response_due + resolution_violated = sla_resolution_due and now > sla_resolution_due # Priorizar resolution si ambos están violados if resolution_violated: violation_type = SLATypeEnum.RESOLUTION - due_at = ticket.sla_resolution_due + due_at = sla_resolution_due else: violation_type = SLATypeEnum.RESPONSE - due_at = ticket.sla_response_due + due_at = sla_response_due hours_overdue = (now - due_at).total_seconds() / 3600 if due_at else 0 - # Cargar relaciones - await db.refresh(ticket, ['created_by', 'assigned_to', 'category']) - + # Las relaciones ya están cargadas por selectinload violations.append(SLAViolationResponse( ticket=TicketBasicInfo( id=ticket.id, @@ -453,17 +460,17 @@ async def get_sla_violations( sla_resolution_hours=ticket.category.sla_resolution_hours ) if ticket.category else None, created_by=UserBasicInfo( - id=ticket.created_by.id, - first_name=ticket.created_by.first_name, - last_name=ticket.created_by.last_name, - email=ticket.created_by.email + id=ticket.created_by_user.id, + first_name=ticket.created_by_user.first_name, + last_name=ticket.created_by_user.last_name, + email=ticket.created_by_user.email ), assigned_to=UserBasicInfo( - id=ticket.assigned_to.id, - first_name=ticket.assigned_to.first_name, - last_name=ticket.assigned_to.last_name, - email=ticket.assigned_to.email - ) if ticket.assigned_to else None, + id=ticket.assigned_to_user.id, + first_name=ticket.assigned_to_user.first_name, + last_name=ticket.assigned_to_user.last_name, + email=ticket.assigned_to_user.email + ) if ticket.assigned_to_user else None, sla_type=violation_type, sla_due_at=due_at, violated_at=due_at, # Se violó en el momento del due diff --git a/backend/app/api/v1/endpoints/tickets.py b/backend/app/api/v1/endpoints/tickets.py index fa3af0c..7955ba2 100644 --- a/backend/app/api/v1/endpoints/tickets.py +++ b/backend/app/api/v1/endpoints/tickets.py @@ -240,14 +240,19 @@ async def create_ticket( async def get_tickets( skip: int = 0, limit: int = 100, - status_filter: Optional[str] = None, + status: Optional[str] = None, + priority: Optional[str] = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ - Obtener tickets + Obtener tickets con filtros opcionales Roles ADMIN/SUPPORT_MANAGER/AGENT: Ven todos los tickets del tenant Roles CLIENT_USER/CLIENT_ADMIN: Solo ven sus propios tickets + + Filtros disponibles: + - status: NEW, IN_PROGRESS, WAITING_CUSTOMER, RESOLVED, CLOSED, REOPENED + - priority: LOW, MEDIUM, HIGH, URGENT """ # Construir query base filtrado por tenant query = select(Ticket).where( @@ -258,14 +263,26 @@ async def get_tickets( if current_user.role in ["CLIENT_USER", "CLIENT_ADMIN"]: query = query.where(Ticket.created_by == current_user.id) - if status_filter: + # Filtro por estado + if status: try: - status_enum = TicketStatus[status_filter.upper()] + status_enum = TicketStatus[status.upper()] query = query.where(Ticket.status == status_enum) except KeyError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid status: {status_filter}" + detail=f"Invalid status: {status}. Valid values: NEW, IN_PROGRESS, WAITING_CUSTOMER, RESOLVED, CLOSED, REOPENED" + ) + + # Filtro por prioridad + if priority: + try: + priority_enum = TicketPriority[priority.upper()] + query = query.where(Ticket.priority == priority_enum) + except KeyError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid priority: {priority}. Valid values: LOW, MEDIUM, HIGH, URGENT" ) query = query.order_by(Ticket.created_at.desc()).offset(skip).limit(limit) diff --git a/backend/app/api/v1/endpoints/tickets.py.backup b/backend/app/api/v1/endpoints/tickets.py.backup deleted file mode 100644 index e4a0fc3..0000000 --- a/backend/app/api/v1/endpoints/tickets.py.backup +++ /dev/null @@ -1,451 +0,0 @@ -""" -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 -from datetime import datetime -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 app.models.category import Category # ✅ CORREGIDO: Era TicketCategory -from app.models.system import System -import uuid - -router = APIRouter() - -# =================================== -# SCHEMAS -# =================================== - -class TicketCreate(BaseModel): - subject: str - description: str - category_id: Optional[str] = None - affected_system_id: Optional[str] = None # ✅ CORREGIDO: Era system_id - priority: str = "MEDIUM" - -class TicketUpdate(BaseModel): - subject: Optional[str] = None - description: Optional[str] = None - status: Optional[str] = None - priority: Optional[str] = None - assigned_to: Optional[str] = None - -class TicketResponse(BaseModel): - id: str - ticket_number: str - subject: str - description: str - status: str - priority: str - category_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 - updated_at: datetime - - class Config: - from_attributes = True - -class TicketCloseRequest(BaseModel): - resolution: Optional[str] = None - - -# =================================== -# TICKET ENDPOINTS -# =================================== - -@router.post("/", response_model=TicketResponse, status_code=status.HTTP_201_CREATED) -async def create_ticket( - ticket: TicketCreate, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Crear un nuevo ticket - """ - try: - # Generar número de ticket único - result = await db.execute( - select(func.count(Ticket.id)).where(Ticket.tenant_id == current_user.tenant_id) - ) - count = result.scalar() or 0 - ticket_number = f"TK-{count + 1:06d}" - - # 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.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(), - tenant_id=current_user.tenant_id, - ticket_number=ticket_number, - subject=ticket.subject, - description=ticket.description, - category_id=category_uuid, - affected_system_id=system_uuid, # ✅ CORREGIDO: Nombre correcto del campo - priority=TicketPriority[ticket.priority.upper()], - created_by=current_user.id, - status=TicketStatus.NEW, - created_at=datetime.utcnow(), - updated_at=datetime.utcnow() - ) - - db.add(db_ticket) - await db.commit() - await db.refresh(db_ticket) - - # ✅ CORREGIDO: Usar affected_system_id en respuesta - return { - "id": str(db_ticket.id), - "ticket_number": db_ticket.ticket_number, - "subject": db_ticket.subject, - "description": db_ticket.description, - "status": db_ticket.status.value, - "priority": db_ticket.priority.value, - "category_id": str(db_ticket.category_id) if db_ticket.category_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, - "updated_at": db_ticket.updated_at - } - - except ValueError as e: - await db.rollback() - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid UUID format: {str(e)}" - ) - except Exception as e: - await db.rollback() - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Error creating ticket: {str(e)}" - ) - - -@router.get("/", response_model=List[TicketResponse]) -async def get_tickets( - skip: int = 0, - limit: int = 100, - status_filter: Optional[str] = None, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Obtener tickets del usuario actual - """ - query = select(Ticket).where( - Ticket.tenant_id == current_user.tenant_id, - Ticket.created_by == current_user.id - ) - - if status_filter: - try: - status_enum = TicketStatus[status_filter.upper()] - query = query.where(Ticket.status == status_enum) - except KeyError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid status: {status_filter}" - ) - - query = query.order_by(Ticket.created_at.desc()).offset(skip).limit(limit) - - result = await db.execute(query) - tickets = result.scalars().all() - - # ✅ CORREGIDO: Usar affected_system_id - return [ - { - "id": str(t.id), - "ticket_number": t.ticket_number, - "subject": t.subject, - "description": t.description, - "status": t.status.value, - "priority": t.priority.value, - "category_id": str(t.category_id) if t.category_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, - "updated_at": t.updated_at - } - for t in tickets - ] - - -@router.get("/{ticket_id}", response_model=TicketResponse) -async def get_ticket( - ticket_id: str, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Obtener un ticket específico - """ - try: - ticket_uuid = uuid.UUID(ticket_id) - except ValueError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid ticket ID format" - ) - - query = select(Ticket).where( - Ticket.id == ticket_uuid, - Ticket.tenant_id == current_user.tenant_id, - Ticket.created_by == current_user.id - ) - - result = await db.execute(query) - ticket = result.scalars().first() - - if not ticket: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Ticket {ticket_id} not found" - ) - - # ✅ CORREGIDO: Usar affected_system_id - return { - "id": str(ticket.id), - "ticket_number": ticket.ticket_number, - "subject": ticket.subject, - "description": ticket.description, - "status": ticket.status.value, - "priority": ticket.priority.value, - "category_id": str(ticket.category_id) if ticket.category_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, - "updated_at": ticket.updated_at - } - - -@router.patch("/{ticket_id}", response_model=TicketResponse) -async def update_ticket( - ticket_id: str, - ticket_update: TicketUpdate, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Actualizar un ticket - """ - try: - ticket_uuid = uuid.UUID(ticket_id) - except ValueError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid ticket ID format" - ) - - query = select(Ticket).where( - Ticket.id == ticket_uuid, - Ticket.tenant_id == current_user.tenant_id, - Ticket.created_by == current_user.id - ) - - result = await db.execute(query) - db_ticket = result.scalars().first() - - if not db_ticket: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Ticket {ticket_id} not found" - ) - - try: - update_data = ticket_update.dict(exclude_unset=True) - - for field, value in update_data.items(): - if field == "status" and value: - setattr(db_ticket, field, TicketStatus[value.upper()]) - elif field == "priority" and value: - setattr(db_ticket, field, TicketPriority[value.upper()]) - elif field == "assigned_to" and value: - setattr(db_ticket, field, uuid.UUID(value)) - else: - setattr(db_ticket, field, value) - - db_ticket.updated_at = datetime.utcnow() - - 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, - "subject": db_ticket.subject, - "description": db_ticket.description, - "status": db_ticket.status.value, - "priority": db_ticket.priority.value, - "category_id": str(db_ticket.category_id) if db_ticket.category_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, - "updated_at": db_ticket.updated_at - } - - except Exception as e: - await db.rollback() - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Error updating ticket: {str(e)}" - ) - - -@router.patch("/{ticket_id}/close", response_model=TicketResponse) -async def close_ticket( - ticket_id: str, - close_request: TicketCloseRequest, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Cerrar un ticket - """ - try: - ticket_uuid = uuid.UUID(ticket_id) - except ValueError: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid ticket ID format" - ) - - query = select(Ticket).where( - Ticket.id == ticket_uuid, - Ticket.tenant_id == current_user.tenant_id, - Ticket.created_by == current_user.id - ) - - result = await db.execute(query) - db_ticket = result.scalars().first() - - if not db_ticket: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Ticket {ticket_id} not found" - ) - - try: - db_ticket.status = TicketStatus.CLOSED - db_ticket.updated_at = datetime.utcnow() - - 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, - "subject": db_ticket.subject, - "description": db_ticket.description, - "status": db_ticket.status.value, - "priority": db_ticket.priority.value, - "category_id": str(db_ticket.category_id) if db_ticket.category_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, - "updated_at": db_ticket.updated_at - } - - except Exception as e: - await db.rollback() - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Error closing ticket: {str(e)}" - ) - - -# =================================== -# COMMENT ENDPOINTS (placeholder) -# =================================== - -@router.get("/{ticket_id}/comments") -async def get_ticket_comments( - ticket_id: str, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Obtener comentarios de un ticket - """ - return [] - - -@router.post("/{ticket_id}/comments", status_code=status.HTTP_201_CREATED) -async def create_comment( - ticket_id: str, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Agregar un comentario a un ticket - """ - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="Comments not yet implemented" - ) - - -# =================================== -# ATTACHMENT ENDPOINTS (placeholder) -# =================================== - -@router.get("/{ticket_id}/attachments") -async def get_ticket_attachments( - ticket_id: str, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Obtener adjuntos de un ticket - """ - return [] - - -@router.post("/{ticket_id}/attachments", status_code=status.HTTP_201_CREATED) -async def upload_attachment( - ticket_id: str, - file: UploadFile = File(...), - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """ - Subir un archivo adjunto a un ticket - """ - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="File uploads not yet implemented" - ) \ No newline at end of file diff --git a/backend/migrations/versions/fix_client_profiles_timestamps.py b/backend/migrations/versions/fix_client_profiles_timestamps.py new file mode 100644 index 0000000..5639b6e --- /dev/null +++ b/backend/migrations/versions/fix_client_profiles_timestamps.py @@ -0,0 +1,47 @@ +"""Fix client_profiles timestamps to use server defaults + +Revision ID: fix_client_timestamps +Revises: a1b2c3d4e5f6 +Create Date: 2026-02-17 12:05:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'fix_client_timestamps' +down_revision = 'a1b2c3d4e5f6' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Modificar created_at para usar server_default + op.alter_column('client_profiles', 'created_at', + existing_type=sa.DateTime(timezone=True), + nullable=False, + server_default=sa.text('now()') + ) + + # Modificar updated_at para usar server_default + op.alter_column('client_profiles', 'updated_at', + existing_type=sa.DateTime(timezone=True), + nullable=False, + server_default=sa.text('now()') + ) + + +def downgrade() -> None: + # Remover server_default + op.alter_column('client_profiles', 'created_at', + existing_type=sa.DateTime(timezone=True), + nullable=False, + server_default=None + ) + + op.alter_column('client_profiles', 'updated_at', + existing_type=sa.DateTime(timezone=True), + nullable=False, + server_default=None + ) diff --git a/frontend-client/src/routes/profile/+page.svelte.backup b/frontend-client/src/routes/profile/+page.svelte.backup deleted file mode 100644 index 3887d39..0000000 --- a/frontend-client/src/routes/profile/+page.svelte.backup +++ /dev/null @@ -1,1062 +0,0 @@ - - - - Mi Perfil - ServiceManager - - -
- -
-

Mi Perfil

-

Gestiona tu información personal y configuración empresarial

-
- - -
- -
- - -
- - {#if activeTab === 'personal'} -
-
-

Información Personal

-

Actualiza tu información básica

-
- -
-
-
-
- - - {#if profileErrors.firstName} -

{profileErrors.firstName}

- {/if} -
- -
- - - {#if profileErrors.lastName} -

{profileErrors.lastName}

- {/if} -
-
- -
- - -

- El correo electrónico no puede ser modificado. Contacta al administrador si - necesitas cambiarlo. -

-
- -
- -
-
-
-
- {/if} - - - {#if activeTab === 'general'} -
-
-

Información Empresarial

-

Datos generales de tu empresa

-
- -
-
- -
-

Información General

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - - {#if businessProfileErrors.rfc} -

{businessProfileErrors.rfc}

- {/if} -
- -
- - -
-
-
- - -
-

Ubicación

-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - - {#if businessProfileErrors.postal_code} -

{businessProfileErrors.postal_code}

- {/if} -
- -
- - -
-
-
- - -
-

Representantes

-
-
- - -
- -
- - -
-
-
- - -
-

Configuración

-
-
- - -
- -
- - -
- -
- -