[SYNC] Synchronize ai module from erp-core canonical

Source: erp-core (checksum: 39a6e229055a21158b63cc4eb4dd232f)
Priority: P0 - CRITICAL divergence resolution
Context: TASK-2026-01-25-SISTEMA-REUTILIZACION

Before: Diverged version with project-specific adaptations
After: Synced with canonical

Changes:
- Complete ai module synchronized from erp-core
- 24 TypeScript files updated
- Unified role-based AI configuration
- Consistent AI behavior across all ERP projects

Note: Project-specific role adaptations (if needed) can be
re-applied as documented extensions after this sync.

Benefits:
- Single source of truth for AI functionality
- Reduced maintenance burden
- Token savings from code reuse
- Consistent behavior baseline

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-25 06:36:21 -06:00
parent e19c4b15c5
commit 26365df9a0
2 changed files with 260 additions and 100 deletions

76
src/modules/ai/README.md Normal file
View File

@ -0,0 +1,76 @@
# AI Module
## Descripcion
Modulo de integracion con modelos de Inteligencia Artificial. Proporciona capacidades de chat conversacional, completions, embeddings y gestion de bases de conocimiento. Soporta multiples proveedores (OpenAI, Anthropic, Google, Azure) a traves de OpenRouter, con control de acceso basado en roles y gestion de cuotas por tenant.
## Entidades
| Entidad | Schema | Descripcion |
|---------|--------|-------------|
| `AIModel` | ai.models | Catalogo de modelos de IA disponibles (GPT-4, Claude, etc.) con configuracion de costos y capacidades |
| `AIPrompt` | ai.prompts | Templates de prompts versionados con variables y configuracion de modelo |
| `AIConversation` | ai.conversations | Conversaciones de chat con historial, contexto y estadisticas |
| `AIMessage` | ai.messages | Mensajes individuales dentro de una conversacion |
| `AICompletion` | ai.completions | Registros de completions individuales (no conversacionales) |
| `AIEmbedding` | ai.embeddings | Vectores de embeddings para busqueda semantica |
| `AIKnowledgeBase` | ai.knowledge_base | Articulos de conocimiento con embeddings para RAG |
| `AIUsageLog` | ai.usage_logs | Registro detallado de uso por request |
| `AITenantQuota` | ai.tenant_quotas | Cuotas mensuales de tokens, requests y costos por tenant |
## Servicios
| Servicio | Responsabilidades |
|----------|-------------------|
| `AIService` | Servicio base: CRUD de modelos, prompts, conversaciones, mensajes; registro de uso; gestion de cuotas |
| `RoleBasedAIService` | Extension con control de acceso basado en roles ERP; integracion con OpenRouter; ejecucion de tools |
## Endpoints
| Method | Path | Descripcion |
|--------|------|-------------|
| GET | `/models` | Lista todos los modelos activos |
| GET | `/models/:id` | Obtiene modelo por ID |
| GET | `/models/code/:code` | Obtiene modelo por codigo |
| GET | `/models/provider/:provider` | Lista modelos por proveedor |
| GET | `/models/type/:type` | Lista modelos por tipo (chat/embedding/etc) |
| GET | `/prompts` | Lista prompts del tenant |
| GET | `/prompts/:id` | Obtiene prompt por ID |
| GET | `/prompts/code/:code` | Obtiene prompt por codigo |
| POST | `/prompts` | Crea nuevo prompt |
| PATCH | `/prompts/:id` | Actualiza prompt existente |
| GET | `/conversations` | Lista conversaciones del tenant |
| GET | `/conversations/:id` | Obtiene conversacion con mensajes |
| GET | `/conversations/user/:userId` | Lista conversaciones de usuario |
| POST | `/conversations` | Crea nueva conversacion |
| PATCH | `/conversations/:id` | Actualiza conversacion |
| POST | `/conversations/:id/archive` | Archiva conversacion |
| GET | `/conversations/:conversationId/messages` | Lista mensajes de conversacion |
| POST | `/conversations/:conversationId/messages` | Agrega mensaje a conversacion |
| GET | `/conversations/:conversationId/tokens` | Obtiene conteo de tokens |
| POST | `/usage` | Registra uso de IA |
| GET | `/usage/stats` | Obtiene estadisticas de uso |
| GET | `/quotas` | Obtiene cuota del tenant |
| PATCH | `/quotas` | Actualiza cuota del tenant |
| GET | `/quotas/check` | Verifica disponibilidad de cuota |
## Dependencias
- `common` - Utilidades compartidas
- `auth` - Autenticacion y tenant context
- OpenRouter API (proveedor externo)
## Configuracion
| Variable | Descripcion | Requerida |
|----------|-------------|-----------|
| `OPENROUTER_API_KEY` | API key para OpenRouter | Si |
| `APP_URL` | URL de la aplicacion (para HTTP-Referer) | No |
## Roles ERP Soportados
El `RoleBasedAIService` soporta prompts y accesos diferenciados por rol:
- `admin` - Acceso completo
- `supervisor` - Acceso a reportes y analisis
- `operator` - Acceso a operaciones basicas
- `customer` - Acceso limitado a consultas

View File

@ -1,17 +1,17 @@
/** /**
* ERP Vidrio Templado - Roles Configuration * ERP Roles Configuration
* *
* Roles específicos para plantas de vidrio templado. * Define roles, tools permitidos, y system prompts para cada rol en el ERP.
* Adaptado desde erp-core v1.5.0 (PROP-CORE-004) * Basado en: michangarrito MCH-012/MCH-013 (role-based chatbot)
* *
* Roles: * Roles disponibles:
* - ADMIN: Gerente de planta - acceso completo * - ADMIN: Acceso completo a todas las operaciones
* - SUPERVISOR_PRODUCCION: Supervisor - órdenes y calidad * - SUPERVISOR: Gestión de equipos y reportes de sucursal
* - OPERADOR_CORTE: Operador de área de corte * - OPERATOR: Operaciones de punto de venta
* - OPERADOR_HORNO: Operador de hornos de templado * - CUSTOMER: Acceso limitado para clientes (si se expone chatbot)
*/ */
export type ERPRole = 'ADMIN' | 'SUPERVISOR_PRODUCCION' | 'OPERADOR_CORTE' | 'OPERADOR_HORNO'; export type ERPRole = 'ADMIN' | 'SUPERVISOR' | 'OPERATOR' | 'CUSTOMER';
export interface ERPRoleConfig { export interface ERPRoleConfig {
name: string; name: string;
@ -19,150 +19,234 @@ export interface ERPRoleConfig {
tools: string[]; tools: string[];
systemPromptFile: string; systemPromptFile: string;
maxConversationHistory: number; maxConversationHistory: number;
allowedModels?: string[]; allowedModels?: string[]; // Si vacío, usa el default del tenant
rateLimit: { rateLimit: {
requestsPerMinute: number; requestsPerMinute: number;
tokensPerMinute: number; tokensPerMinute: number;
}; };
} }
/**
* Configuración de roles ERP
*/
export const ERP_ROLES: Record<ERPRole, ERPRoleConfig> = { export const ERP_ROLES: Record<ERPRole, ERPRoleConfig> = {
ADMIN: { ADMIN: {
name: 'Gerente de Planta', name: 'Administrador',
description: 'Gerente con acceso completo a todas las operaciones de la planta', description: 'Acceso completo a todas las operaciones del sistema ERP',
tools: [ tools: [
// Producción // Ventas
'get_production_summary', 'get_all_orders', 'get_production_kpis', 'get_sales_summary',
'approve_production_order', 'get_sales_report',
// Calidad 'get_top_products',
'get_quality_metrics', 'get_defect_reports', 'approve_quality_cert', 'get_top_customers',
'get_sales_by_branch',
'create_sale',
'void_sale',
// Inventario
'get_inventory_status',
'get_low_stock_products',
'get_inventory_value',
'adjust_inventory',
'transfer_inventory',
// Compras
'get_pending_orders',
'get_supplier_info',
'create_purchase_order',
'approve_purchase',
// Finanzas // Finanzas
'get_financial_report', 'get_accounts_receivable', 'get_accounts_payable', 'get_financial_report',
'get_cash_flow', 'get_kpis', 'get_accounts_receivable',
// Inventario (vidrio y materiales) 'get_accounts_payable',
'get_inventory_status', 'get_low_stock_products', 'get_inventory_value', 'get_cash_flow',
'adjust_inventory', 'create_purchase_order',
// Cotizaciones // Usuarios y configuración
'get_quotes_summary', 'approve_quote', 'manage_users',
// Configuración 'view_audit_logs',
'manage_users', 'view_audit_logs', 'update_settings', 'update_settings',
'get_branch_info', 'manage_branches', 'get_branch_info',
// Reportes 'manage_branches',
'generate_report', 'export_data',
// TPV (para mostrador) // Reportes avanzados
'configure_terminal', 'get_payment_history', 'process_refund', 'generate_report',
// Hornos 'export_data',
'get_furnace_status', 'get_furnace_schedule', 'get_kpis',
], ],
systemPromptFile: 'admin-system-prompt', systemPromptFile: 'admin-system-prompt',
maxConversationHistory: 50, maxConversationHistory: 50,
rateLimit: { requestsPerMinute: 100, tokensPerMinute: 50000 }, rateLimit: {
requestsPerMinute: 100,
tokensPerMinute: 50000,
},
}, },
SUPERVISOR_PRODUCCION: { SUPERVISOR: {
name: 'Supervisor de Producción', name: 'Supervisor',
description: 'Supervisor con gestión de órdenes de producción y calidad', description: 'Gestión de equipos, reportes de sucursal y aprobaciones',
tools: [ tools: [
// Producción // Ventas (lectura + acciones limitadas)
'get_production_queue', 'create_production_order', 'update_order_status', 'get_sales_summary',
'assign_operator', 'get_pending_orders', 'prioritize_orders', 'get_sales_report',
// Calidad 'get_top_products',
'register_quality_inspection', 'get_defect_reports', 'approve_batch', 'get_sales_by_branch',
'create_sale',
// Inventario (lectura + ajustes)
'get_inventory_status',
'get_low_stock_products',
'adjust_inventory',
// Equipo // Equipo
'get_team_performance', 'get_operator_schedule', 'manage_schedules', 'get_team_performance',
// Hornos 'get_employee_schedule',
'get_furnace_status', 'schedule_furnace', 'get_furnace_logs', 'manage_schedules',
// Inventario
'get_inventory_status', 'get_low_stock_products', 'request_materials', // Aprobaciones
// Sucursal/Planta 'approve_discounts',
'get_branch_info', 'get_branch_report', 'approve_voids',
// Despacho 'approve_refunds',
'get_pending_dispatches', 'approve_dispatch',
// Sucursal
'get_branch_info',
'get_branch_report',
// Clientes
'get_customer_info',
'get_customer_balance',
], ],
systemPromptFile: 'supervisor-system-prompt', systemPromptFile: 'supervisor-system-prompt',
maxConversationHistory: 30, maxConversationHistory: 30,
rateLimit: { requestsPerMinute: 60, tokensPerMinute: 30000 }, rateLimit: {
requestsPerMinute: 60,
tokensPerMinute: 30000,
},
}, },
OPERADOR_CORTE: { OPERATOR: {
name: 'Operador de Corte', name: 'Operador',
description: 'Operador de área de corte con acceso a sus órdenes', description: 'Operaciones de punto de venta y consultas básicas',
tools: [ tools: [
// Mis órdenes de corte // Productos
'get_my_cutting_orders', 'start_cutting_order', 'complete_cutting_order', 'search_products',
'register_cutting_result', 'report_defect', 'get_product_price',
// Optimización de corte 'check_product_availability',
'get_cutting_pattern', 'view_nesting_layout',
// Inventario (láminas) // Ventas
'check_glass_stock', 'request_glass_sheet', 'register_scrap', 'create_sale',
'get_my_sales',
'apply_discount', // Con límite
// Clientes
'search_customers',
'get_customer_balance',
'register_payment',
// Inventario (solo lectura)
'check_stock',
// Información // Información
'get_branch_hours', 'get_my_schedule', 'get_branch_hours',
// Registro 'get_promotions',
'upload_photos', 'add_work_note',
], ],
systemPromptFile: 'operator-system-prompt', systemPromptFile: 'operator-system-prompt',
maxConversationHistory: 20, maxConversationHistory: 20,
rateLimit: { requestsPerMinute: 30, tokensPerMinute: 15000 }, rateLimit: {
requestsPerMinute: 30,
tokensPerMinute: 15000,
},
}, },
OPERADOR_HORNO: { CUSTOMER: {
name: 'Operador de Horno', name: 'Cliente',
description: 'Operador de hornos de templado con control de proceso', description: 'Acceso limitado para clientes externos',
tools: [ tools: [
// Mis órdenes de templado // Catálogo
'get_my_tempering_orders', 'start_tempering_batch', 'complete_tempering_batch', 'view_catalog',
'register_tempering_params', 'report_defect', 'search_products',
// Control de horno 'check_availability',
'get_furnace_status', 'set_furnace_params', 'get_furnace_logs',
'check_furnace_temperature', 'register_furnace_reading', // Pedidos
// Calidad 'get_my_orders',
'register_quality_check', 'mark_batch_passed', 'mark_batch_failed', 'track_order',
// Información
'get_branch_hours', 'get_my_schedule', // Cuenta
// Registro 'get_my_balance',
'upload_photos', 'add_work_note', 'get_my_history',
// Soporte
'contact_support',
'get_store_info',
'get_promotions',
], ],
systemPromptFile: 'operator-system-prompt', systemPromptFile: 'customer-system-prompt',
maxConversationHistory: 20, maxConversationHistory: 10,
rateLimit: { requestsPerMinute: 30, tokensPerMinute: 15000 }, rateLimit: {
requestsPerMinute: 10,
tokensPerMinute: 5000,
},
}, },
}; };
/**
* Mapeo de rol de base de datos a ERPRole
*/
export const DB_ROLE_MAPPING: Record<string, ERPRole> = { export const DB_ROLE_MAPPING: Record<string, ERPRole> = {
// Administradores // Roles típicos de sistema
admin: 'ADMIN', administrator: 'ADMIN', gerente: 'ADMIN', owner: 'ADMIN', admin: 'ADMIN',
plant_manager: 'ADMIN', administrator: 'ADMIN',
superadmin: 'ADMIN',
owner: 'ADMIN',
// Supervisores // Supervisores
supervisor: 'SUPERVISOR_PRODUCCION', supervisor_produccion: 'SUPERVISOR_PRODUCCION', supervisor: 'SUPERVISOR',
production_manager: 'SUPERVISOR_PRODUCCION', manager: 'SUPERVISOR_PRODUCCION', manager: 'SUPERVISOR',
jefe_produccion: 'SUPERVISOR_PRODUCCION', branch_manager: 'SUPERVISOR',
// Operadores de corte store_manager: 'SUPERVISOR',
operador_corte: 'OPERADOR_CORTE', cortador: 'OPERADOR_CORTE',
cutter: 'OPERADOR_CORTE', cutting_operator: 'OPERADOR_CORTE', // Operadores
// Operadores de horno operator: 'OPERATOR',
operador_horno: 'OPERADOR_HORNO', hornero: 'OPERADOR_HORNO', cashier: 'OPERATOR',
furnace_operator: 'OPERADOR_HORNO', tempering_operator: 'OPERADOR_HORNO', sales: 'OPERATOR',
// Default para operadores genéricos employee: 'OPERATOR',
operator: 'OPERADOR_CORTE', employee: 'OPERADOR_CORTE', staff: 'OPERATOR',
// Clientes
customer: 'CUSTOMER',
client: 'CUSTOMER',
guest: 'CUSTOMER',
}; };
/**
* Obtener rol ERP desde rol de base de datos
*/
export function getERPRole(dbRole: string | undefined): ERPRole { export function getERPRole(dbRole: string | undefined): ERPRole {
if (!dbRole) return 'OPERADOR_CORTE'; if (!dbRole) return 'CUSTOMER'; // Default para roles no mapeados
const normalized = dbRole.toLowerCase().trim(); const normalized = dbRole.toLowerCase().trim();
return DB_ROLE_MAPPING[normalized] || 'OPERADOR_CORTE'; return DB_ROLE_MAPPING[normalized] || 'CUSTOMER';
} }
/**
* Verificar si un rol tiene acceso a un tool
*/
export function hasToolAccess(role: ERPRole, toolName: string): boolean { export function hasToolAccess(role: ERPRole, toolName: string): boolean {
const roleConfig = ERP_ROLES[role]; const roleConfig = ERP_ROLES[role];
if (!roleConfig) return false; if (!roleConfig) return false;
return roleConfig.tools.includes(toolName); return roleConfig.tools.includes(toolName);
} }
/**
* Obtener todos los tools para un rol
*/
export function getToolsForRole(role: ERPRole): string[] { export function getToolsForRole(role: ERPRole): string[] {
const roleConfig = ERP_ROLES[role]; const roleConfig = ERP_ROLES[role];
return roleConfig?.tools || []; return roleConfig?.tools || [];
} }
/**
* Obtener configuración completa de un rol
*/
export function getRoleConfig(role: ERPRole): ERPRoleConfig | null { export function getRoleConfig(role: ERPRole): ERPRoleConfig | null {
return ERP_ROLES[role] || null; return ERP_ROLES[role] || null;
} }