Módulo: Soporte + Guía IA
Scope
| Tipo de organización | Acceso |
|---|---|
| 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ón | Disponible | Permiso |
|---|---|---|
| Ver casos de soporte | ✅ | support:read |
| Crear caso de soporte | ✅ | support:manage |
| Responder caso | ✅ | support:manage |
| Usar guía IA | ✅ | ai:use-guide |
| Ver historial IA | ✅ | ai:use-guide |
| Crear handoff a soporte humano | ✅ | ai:use-guide + support:manage |
Agencia (AGENCY)
| Acción | Disponible | Permiso |
|---|---|---|
| Ver casos de soporte | ✅ | support:read |
| Crear caso de soporte | ✅ | support:manage |
| Responder caso | ✅ | support:manage |
| Usar guía IA | ✅ | ai:use-guide |
| Ver historial IA | ✅ | ai:use-guide |
| Crear handoff a soporte humano | ✅ | ai:use-guide + support:manage |
Reclutador independiente (RECRUITER)
| Acción | Disponible | Permiso |
|---|---|---|
| Ver casos de soporte | ✅ | support:read |
| Crear caso de soporte | ✅ | support:manage |
| Responder caso | ✅ | support:manage |
| Usar guía IA | ✅ | ai:use-guide |
| Ver historial IA | ✅ | ai:use-guide |
| Crear handoff a soporte humano | ✅ | ai: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 permitidas | Acciones restringidas (bloqueadas) |
|---|---|
| answer_faqs | approve_profile |
| explain_vacancy_status | release_wallet_funds |
| explain_candidate_status | change_reputation_score |
| explain_dispute_status | resolve_dispute |
| summarize_conversation | decide_candidate_authorship |
| collect_support_context | close_sensitive_case |
| draft_support_reply | change_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
| Campo | Tipo | Descripción |
|---|---|---|
| id | String | UUID del caso |
| organizationId | String | Organización propietaria |
| subject | String | Asunto del caso |
| description | String | Descripción completa |
| status | SupportCaseStatus | Estado (open, waitingSupport, waitingTenant, resolved, closed) |
| priority | SupportCasePriority | Prioridad (low, medium, high, urgent) |
| originType | SupportCaseOriginType | Origen (general, vacancy, candidate, dispute, etc.) |
| createdBy | SupportUserSummary | Usuario que creó el caso |
| messageCount | int | Número de mensajes |
| lastActivityAt | DateTime | Última actividad |
| messages | List<SupportCaseMessage> | Mensajes del caso (en detalle) |
AiConversation
| Campo | Tipo | Descripción |
|---|---|---|
| conversationId | String | UUID de la conversación |
| displayTitle | String | Título derivado del primer mensaje |
| contextType | String? | Tipo de contexto (GENERAL, VACANCY, etc.) |
| messageCount | int | Número de mensajes |
| lastMessageAt | DateTime | Último mensaje |
AiChatMessage (UI state)
| Campo | Tipo | Descripción |
|---|---|---|
| content | String | Texto del mensaje |
| isUser | bool | Si es del usuario o del asistente |
| timestamp | DateTime | Timestamp |
| isLoading | bool | Si es un placeholder de "escribiendo..." |
UseCases
| UseCase | Parámetros | Retorno | Descripción |
|---|---|---|---|
| ListSupportCasesUseCase | page, search, status, priority | PaginatedResponse<SupportCase> | Lista casos paginados |
| CreateSupportCaseUseCase | subject, description, priority? | SupportCase | Crea un caso nuevo |
| ReplyToCaseUseCase | supportCaseId, message | SupportCase | Responde a un caso |
| ListAiConversationsUseCase | page, pageSize, channel? | PaginatedResponse<AiConversation> | Lista historial IA |
| CreateAiConversationUseCase | channel, topic?, contextType? | AiConversationSession | Crea conversación |
| SendAiMessageUseCase | conversationId, message | AiGuideMessageResponse | Envía mensaje a IA |
| GetAiTranscriptUseCase | conversationId | AiTranscript | Lee 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étodo | Endpoint | Descripción |
|---|---|---|
| GET | /support/cases | Listar casos (paginado, filtros) |
| GET | /support/cases/:id | Detalle de caso |
| POST | /support/cases | Crear caso |
| POST | /support/cases/:id/messages | Responder caso |
| PATCH | /support/cases/:id/read | Marcar como leído |
AI Guide (sin prefijo /api)
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /ai/guide/conversations | Listar conversaciones (paginado) |
| POST | /ai/guide/conversations | Crear conversación |
| GET | /ai/guide/conversations/:id | Leer transcript |
| POST | /ai/guide/conversations/:id/messages | Enviar mensaje |
| POST | /ai/guide/conversations/:id/handoff | Crear handoff a soporte |
| POST | /ai/guide/conversations/:id/feedback | Registrar feedback |
Nota: Estas rutas NO llevan el prefijo
/api. Los datasources usan URLs absolutas (https://service-gateway.contratta.mx/...) para evitar que elbaseUrlde Dio agregue/api.
Widgets principales
| Widget | Responsabilidad |
|---|---|
SupportPage | Entry point con BlocProviders y tabs |
SupportCasesList | Lista de casos con infinite scroll |
SupportHistoryList | Lista de conversaciones IA con infinite scroll |
SupportAiBanner | Banner informativo de restricciones del AI |
SupportCreateCaseSheet | Bottom sheet para crear caso |
SupportCaseDetailPage | Detalle de caso con mensajes y reply |
AiGuideChatPage | Chat fullscreen con la guía IA |
Dependencias
flutter_bloc— State management (Cubit pattern)flutter_markdown— Rendering de respuestas IA en Markdownintl— Formateo de fechasdio— HTTP client (via ApiClient centralizado)
Registro DI (get_it)
- Datasources:
LazySingleton - Repositories:
LazySingleton - UseCases:
LazySingleton - Cubits:
Factory(nueva instancia por pantalla)
Manejo de errores
- Datasource — deja propagar
DioExceptionsin catch - Repository — captura
DioExceptionyAppException, mapea aFailuretipados viaNetworkErrorHandler - Cubit — emite
errorMessageen el estado - UI —
BlocBuildermuestra 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/historyPagey acumula items hasNextPage/hasNextHistoryPagese derivan delPageMetadel 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*.