Saltar al contenido principal
Versión: 1.0

Módulo: Soporte + Guía IA

Scope

Tipo de organizaciónAcceso
Empresa
Agencia
Reclutador independiente

Disponible para los tres tipos de organización. Requiere permiso support:read para ver casos y support:manage para crear/responder. La Guía IA requiere ai:use-guide.

Permisos por rol

Empresa (COMPANY)

AcciónDisponiblePermiso
Ver casos de soportesupport:read
Crear caso de soportesupport:manage
Responder casosupport:manage
Usar guía IAai:use-guide
Ver historial IAai:use-guide
Crear handoff a soporte humanoai:use-guide + support:manage

Agencia (AGENCY)

AcciónDisponiblePermiso
Ver casos de soportesupport:read
Crear caso de soportesupport:manage
Responder casosupport:manage
Usar guía IAai:use-guide
Ver historial IAai:use-guide
Crear handoff a soporte humanoai:use-guide + support:manage

Reclutador independiente (RECRUITER)

AcciónDisponiblePermiso
Ver casos de soportesupport:read
Crear caso de soportesupport:manage
Responder casosupport:manage
Usar guía IAai:use-guide
Ver historial IAai:use-guide
Crear handoff a soporte humanoai:use-guide + support:manage

Nota: Los usuarios de tipo INTERNAL (sistema) no pueden usar el chat de la guía IA — solo pueden revisar historial desde rutas system.


¿Qué es?

El módulo de Soporte permite a los usuarios abrir casos de ayuda, dar seguimiento a respuestas del equipo de soporte, y conversar con un asistente de IA (Guía Contratta) que orienta sobre flujos de la plataforma sin ejecutar acciones sensibles.

El módulo tiene dos secciones principales:

  • Casos de soporte — CRUD de tickets con mensajería bidireccional
  • Historial IA — Conversaciones previas con el asistente inteligente

¿Qué puede hacer el usuario?

Casos de soporte

  • Ver lista paginada de casos con infinite scroll
  • Crear un nuevo caso (asunto + descripción)
  • Ver detalle de un caso con historial de mensajes
  • Responder a un caso existente
  • Filtrar por estado y prioridad
  • Buscar por texto

Guía IA

  • Abrir una nueva conversación con el asistente
  • Hacer preguntas sobre vacantes, candidatos, disputas, wallet, facturación y flujos de la plataforma
  • Ver preguntas sugeridas al iniciar
  • Ver historial de conversaciones previas con infinite scroll
  • Abrir y continuar una conversación existente
  • Ver respuestas con formato Markdown (negritas, listas, títulos)
  • Ver indicador de "Memoria disponible" cuando el contexto está activo

Flujo del usuario

1. Accede a "Soporte" desde el drawer lateral
2. Ve la lista de casos de soporte (tab "Casos de soporte")
3. Puede crear un caso nuevo → bottom sheet con formulario
4. Puede tocar un caso → ve detalle con mensajes → puede responder
5. Cambia al tab "Historial" → ve conversaciones previas con la IA
6. Toca una conversación → se abre el chat con transcript completo → puede seguir escribiendo
7. Presiona "Abrir guía IA" → abre chat fullscreen para nueva conversación
8. Escribe una pregunta o toca una sugerida → recibe respuesta real del AI
9. Al cerrar el chat, el historial se actualiza automáticamente

Reglas de negocio

  • La Guía IA no puede ejecutar acciones sensibles: aprobar perfiles, liberar fondos, cambiar scores, resolver disputas, decidir autoría ni cerrar casos sensibles
  • La Guía IA puede explicar flujos, resumir contexto, preparar handoff a soporte humano
  • Cada conversación se crea en el backend con canal internal_chat
  • Las respuestas del AI se renderizan como Markdown (negritas, listas, encabezados)
  • El historial usa paginación con infinite scroll (20 items por página)
  • Los casos de soporte usan paginación con infinite scroll (20 items por página)
  • Los casos no se pueden responder si están en estado CLOSED

Restricciones del AI Guide

Capacidades permitidasAcciones restringidas (bloqueadas)
answer_faqsapprove_profile
explain_vacancy_statusrelease_wallet_funds
explain_candidate_statuschange_reputation_score
explain_dispute_statusresolve_dispute
summarize_conversationdecide_candidate_authorship
collect_support_contextclose_sensitive_case
draft_support_replychange_sensitive_business_state
prepare_handoff

Arquitectura

Sigue Clean Architecture con estructura feature-first:

lib/features/support/
├── application/
│ ├── ai_guide_cubit.dart
│ ├── ai_guide_state.dart
│ ├── support_cases_cubit.dart
│ └── support_cases_state.dart
├── data/
│ ├── datasources/
│ │ ├── ai_guide_remote_datasource.dart
│ │ └── support_remote_datasource.dart
│ ├── models/
│ │ ├── ai_conversation_model.dart
│ │ └── support_case_model.dart
│ └── repositories/
│ ├── ai_guide_repository_impl.dart
│ └── support_repository_impl.dart
├── domain/
│ ├── entities/
│ │ ├── ai_conversation.dart
│ │ ├── paginated_response.dart
│ │ └── support_case.dart
│ ├── repositories/
│ │ └── support_repository.dart
│ └── usecases/
│ ├── ai_guide_usecases.dart
│ ├── create_support_case_usecase.dart
│ ├── list_support_cases_usecase.dart
│ └── reply_to_case_usecase.dart
└── presentation/
├── pages/
│ ├── ai_guide_chat_page.dart
│ ├── support_case_detail_page.dart
│ └── support_page.dart
└── widgets/
├── support_ai_banner.dart
├── support_cases_list.dart
├── support_create_case_sheet.dart
└── support_history_list.dart

Flujo de datos

UI (Widget) → Cubit → UseCase → Repository → RemoteDatasource → API

Los errores se propagan como Failure tipados y se muestran con AppNotification.

Entidades

SupportCase

CampoTipoDescripción
idStringUUID del caso
organizationIdStringOrganización propietaria
subjectStringAsunto del caso
descriptionStringDescripción completa
statusSupportCaseStatusEstado (open, waitingSupport, waitingTenant, resolved, closed)
prioritySupportCasePriorityPrioridad (low, medium, high, urgent)
originTypeSupportCaseOriginTypeOrigen (general, vacancy, candidate, dispute, etc.)
createdBySupportUserSummaryUsuario que creó el caso
messageCountintNúmero de mensajes
lastActivityAtDateTimeÚltima actividad
messagesList<SupportCaseMessage>Mensajes del caso (en detalle)

AiConversation

CampoTipoDescripción
conversationIdStringUUID de la conversación
displayTitleStringTítulo derivado del primer mensaje
contextTypeString?Tipo de contexto (GENERAL, VACANCY, etc.)
messageCountintNúmero de mensajes
lastMessageAtDateTimeÚltimo mensaje

AiChatMessage (UI state)

CampoTipoDescripción
contentStringTexto del mensaje
isUserboolSi es del usuario o del asistente
timestampDateTimeTimestamp
isLoadingboolSi es un placeholder de "escribiendo..."

UseCases

UseCaseParámetrosRetornoDescripción
ListSupportCasesUseCasepage, search, status, priorityPaginatedResponse<SupportCase>Lista casos paginados
CreateSupportCaseUseCasesubject, description, priority?SupportCaseCrea un caso nuevo
ReplyToCaseUseCasesupportCaseId, messageSupportCaseResponde a un caso
ListAiConversationsUseCasepage, pageSize, channel?PaginatedResponse<AiConversation>Lista historial IA
CreateAiConversationUseCasechannel, topic?, contextType?AiConversationSessionCrea conversación
SendAiMessageUseCaseconversationId, messageAiGuideMessageResponseEnvía mensaje a IA
GetAiTranscriptUseCaseconversationIdAiTranscriptLee transcript completo

Estados

SupportCasesState

enum SupportCasesStatus { initial, loading, success, error }

class SupportCasesState {
final SupportCasesStatus status;
final List<SupportCase> cases;
final PageMeta? meta;
final String? errorMessage;
final String searchQuery;
final String statusFilter;
final String priorityFilter;
final bool isCreating;
final bool isReplying;
final bool isLoadingMore; // infinite scroll
final int currentPage;
}

AiGuideState

enum AiGuideStatus { initial, loading, success, error }

class AiGuideState {
final AiGuideStatus status;
final List<AiConversation> conversations;
final PageMeta? conversationsMeta;
final List<AiChatMessage> messages;
final String? activeConversationId;
final bool isSending;
final bool isLoadingHistory;
final bool isLoadingMoreHistory; // infinite scroll
final int historyPage;
final bool memoryAvailable;
}

Endpoints API

Soporte (sin prefijo /api)

MétodoEndpointDescripción
GET/support/casesListar casos (paginado, filtros)
GET/support/cases/:idDetalle de caso
POST/support/casesCrear caso
POST/support/cases/:id/messagesResponder caso
PATCH/support/cases/:id/readMarcar como leído

AI Guide (sin prefijo /api)

MétodoEndpointDescripción
GET/ai/guide/conversationsListar conversaciones (paginado)
POST/ai/guide/conversationsCrear conversación
GET/ai/guide/conversations/:idLeer transcript
POST/ai/guide/conversations/:id/messagesEnviar mensaje
POST/ai/guide/conversations/:id/handoffCrear handoff a soporte
POST/ai/guide/conversations/:id/feedbackRegistrar feedback

Nota: Estas rutas NO llevan el prefijo /api. Los datasources usan URLs absolutas (https://service-gateway.contratta.mx/...) para evitar que el baseUrl de Dio agregue /api.

Widgets principales

WidgetResponsabilidad
SupportPageEntry point con BlocProviders y tabs
SupportCasesListLista de casos con infinite scroll
SupportHistoryListLista de conversaciones IA con infinite scroll
SupportAiBannerBanner informativo de restricciones del AI
SupportCreateCaseSheetBottom sheet para crear caso
SupportCaseDetailPageDetalle de caso con mensajes y reply
AiGuideChatPageChat fullscreen con la guía IA

Dependencias

  • flutter_bloc — State management (Cubit pattern)
  • flutter_markdown — Rendering de respuestas IA en Markdown
  • intl — Formateo de fechas
  • dio — HTTP client (via ApiClient centralizado)

Registro DI (get_it)

  • Datasources: LazySingleton
  • Repositories: LazySingleton
  • UseCases: LazySingleton
  • Cubits: Factory (nueva instancia por pantalla)

Manejo de errores

  1. Datasource — deja propagar DioException sin catch
  2. Repository — captura DioException y AppException, mapea a Failure tipados via NetworkErrorHandler
  3. Cubit — emite errorMessage en el estado
  4. UIBlocBuilder muestra estado de error con botón retry

Paginación (Infinite Scroll)

Ambas listas implementan infinite scroll:

  • NotificationListener<ScrollNotification> detecta cuando el scroll llega a 200px del fondo
  • Dispara loadMore() / loadMoreConversations() en el cubit
  • El cubit guarda currentPage / historyPage y acumula items
  • hasNextPage / hasNextHistoryPage se derivan del PageMeta del backend
  • Un loader circular aparece al pie mientras carga la siguiente página
  • Guards previenen peticiones duplicadas (isLoadingMore / isLoadingMoreHistory)

Localización

Todas las cadenas visibles usan context.l10n.keyName. Las claves relevantes están en lib/l10n/app_es.arb y lib/l10n/app_en.arb con prefijo support*.