{ "role": "VENDOR", "role_detection": { "summary": "La aplicación determina el rol 'VENDOR' revisando las propiedades 'user_role' o 'role' en el objeto de usuario. Este objeto se obtiene de `base44.auth.me()`, que combina datos de Firebase Auth y un perfil de usuario de la base de datos (DataConnect). Existe un mecanismo de anulación para desarrollo que utiliza localStorage.", "fields_used": [ "user.user_role", "user.role", "localStorage.getItem('krow_mock_user_role')" ], "evidence": [ "src/api/krowSDK.js: La función `auth.me()` unifica el objeto de usuario y prioriza el rol mock de localStorage.", "src/pages/Layout.jsx: `const userRole = user?.user_role || user?.role || 'admin';` se usa para determinar qué menú mostrar." ] }, "menu_for_role": [ { "label": "Home", "route": "/VendorDashboard", "page_component": "VendorDashboard", "page_file": "src/pages/VendorDashboard.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Orders", "route": "/VendorOrders", "page_component": "VendorOrders", "page_file": "src/pages/VendorOrders.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Service Rates", "route": "/VendorRates", "page_component": "VendorRates", "page_file": "src/pages/VendorRates.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Invoices", "route": "/Invoices", "page_component": "Invoices", "page_file": "src/pages/Invoices.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Workforce", "route": "/StaffDirectory", "page_component": "StaffDirectory", "page_file": "src/pages/StaffDirectory.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Onboard Staff", "route": "/StaffOnboarding", "page_component": "StaffOnboarding", "page_file": "src/pages/StaffOnboarding.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Team", "route": "/Teams", "page_component": "Teams", "page_file": "src/pages/Teams.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] }, { "label": "Compliance", "route": "/VendorCompliance", "page_component": "VendorCompliance", "page_file": "src/pages/VendorCompliance.jsx", "visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"] } ], "routes_for_role": [ { "route": "/*", "component": "Any page component", "file": "src/pages/index.jsx", "guard": "ProtectedRoute", "role_constraints": "El `ProtectedRoute` solo valida si el usuario está autenticado, no su rol. El control de acceso por rol se realiza a nivel de UI (ocultando menús en `Layout.jsx`) y a nivel de datos (filtrando datos dentro de cada página)." } ], "workflows": [ { "name": "Vendor - Visualizar y filtrar órdenes", "entry": { "label": "Orders", "route": "/VendorOrders" }, "steps": ["1. Usuario navega a la página 'Orders' desde el menú.", "2. La página `VendorOrders.jsx` se renderiza.", "3. Se llama a `base44.entities.Event.list()` para obtener todas las órdenes.", "4. Los datos se filtran en el frontend para mostrar solo las órdenes donde `e.vendor_id === user.id` o `e.vendor_name === user.company_name`.", "5. El usuario utiliza las pestañas para filtrar por estado ('upcoming', 'active', 'past').", "6. El usuario escribe en la barra de búsqueda para filtrar por nombre de evento, cliente o ubicación."], "sdk_calls": ["base44.entities.Event.list"], "data_requirements": ["user.id", "user.company_name"], "known_failure_modes": ["Si una orden no tiene `vendor_id` o `vendor_name` asignado correctamente, no aparecerá en la lista del vendor."], "evidence_files": ["src/pages/VendorOrders.jsx"] }, { "name": "Vendor - Editar una orden", "entry": { "label": "Orders", "route": "/VendorOrders" }, "steps": ["1. En la tabla de órdenes, el usuario hace clic en el ícono 'Edit'.", "2. Navega a la página `EditEvent?id={id}`.", "3. La página `EditEvent.jsx` comprueba el rol con `isVendor = user?.user_role === 'vendor'`.", "4. El formulario de evento (`EventForm`) se renderiza, pero ciertas secciones (probablemente relacionadas con cliente y precios) están deshabilitadas para el vendor (UNKNOWN - requiere análisis de `EventForm`).", "5. El vendor actualiza campos permitidos (ej. notas internas) y guarda.", "6. Se llama a la mutación `updateEvent` con los datos actualizados."], "sdk_calls": ["base44.entities.Event.update"], "data_requirements": ["event.id", "user.user_role"], "known_failure_modes": ["El formulario podría fallar si intenta guardar un campo que no tiene permitido modificar."], "evidence_files": ["src/pages/VendorOrders.jsx", "src/pages/EditEvent.jsx"] }, { "name": "Vendor - Añadir un nuevo miembro de staff", "entry": { "label": "Workforce", "route": "/StaffDirectory" }, "steps": ["1. Usuario navega a `StaffDirectory`.", "2. Hace clic en el botón 'Add New Staff'.", "3. Navega a la página `AddStaff.jsx`.", "4. Rellena el formulario con los detalles del nuevo empleado.", "5. Al guardar, se asume que se llama a `base44.entities.Staff.create` con un payload que DEBE incluir `{ vendor_id: user.id }` para la correcta asignación."], "sdk_calls": ["base44.entities.Staff.create"], "data_requirements": ["user.id", "user.company_name"], "payload_notes": ["Es crítico que el `vendor_id` del vendor logueado se inyecte en el payload de creación del staff."], "known_failure_modes": ["Si el `vendor_id` no se envía, el nuevo miembro de staff podría quedar 'huérfano', sin ser visible para el vendor."], "evidence_files": ["src/pages/StaffDirectory.jsx", "src/pages/AddStaff.jsx"] }, { "name": "Vendor - Asignar staff a una orden (Smart Assign)", "entry": { "label": "Orders", "route": "/VendorOrders" }, "steps": ["1. En la tabla de órdenes, el usuario hace clic en el ícono de `UserCheck` (Smart Assign).", "2. Se abre el modal `SmartAssignModal.jsx`.", "3. El modal muestra una lista del staff disponible y compatible del vendor.", "4. El vendor selecciona uno o más miembros del staff.", "5. Al confirmar, el modal ejecuta una lógica que culmina en una llamada a `base44.entities.Event.update` para actualizar el campo `assigned_staff` de la orden."], "sdk_calls": ["base44.entities.Event.update"], "data_requirements": ["event.id", "event.shifts", "staff list"], "known_failure_modes": ["El motor de 'Smart Assign' podría fallar si los datos de `shifts` en el evento son inconsistentes o si las tasas (`VendorRate`) no se encuentran."], "evidence_files": ["src/pages/VendorOrders.jsx", "src/components/events/SmartAssignModal.jsx"] }, { "name": "Vendor - Gestionar tarifas de servicio", "entry": { "label": "Service Rates", "route": "/VendorRates" }, "steps": ["1. Usuario navega a `VendorRates`.", "2. La vista `VendorCompanyPricebookView` se renderiza, mostrando tarifarios empresariales (ej. Aramark) y tarifarios personalizados ('Custom Rate Cards').", "3. El vendor puede crear un nuevo tarifario personalizado, asignarle un nombre y definir tarifas por rol.", "4. El vendor puede usar la función 'AI Price Check' que llama a `base44.integrations.Core.InvokeLLM` para obtener un análisis de competitividad."], "sdk_calls": ["base44.entities.VendorRate.list", "base44.integrations.Core.InvokeLLM"], "data_requirements": ["user.company_name"], "known_failure_modes": ["La comparación de tarifas podría ser imprecisa si los nombres de los roles no coinciden exactamente entre los diferentes tarifarios."], "evidence_files": ["src/pages/VendorRates.jsx"] }, { "name": "Vendor - Importar certificados de staff en bloque", "entry": { "label": "Compliance", "route": "/VendorCompliance" }, "steps": ["1. Usuario navega a `VendorCompliance` y hace clic en 'Bulk Import'.", "2. Se abre un diálogo donde el usuario arrastra y suelta múltiples archivos (PDF, JPG).", "3. La función `processBulkFiles` se ejecuta en un bucle por cada archivo.", "4. Cada archivo se sube con `UploadFile` y luego se analiza con `InvokeLLM` para extraer el nombre del titular, fechas, etc.", "5. El sistema intenta hacer un 'fuzzy match' del nombre extraído con la lista de staff del vendor usando `findEmployeeByName`.", "6. La UI muestra los resultados, permitiendo al vendor corregir manualmente los matches fallidos.", "7. Al confirmar, se llama a `base44.entities.Certification.create` por cada certificado verificado."], "sdk_calls": ["base44.integrations.Core.UploadFile", "base44.integrations.Core.InvokeLLM", "base44.entities.Certification.create"], "data_requirements": ["staff list", "user.id", "user.company_name"], "known_failure_modes": ["El 'fuzzy match' de nombres puede fallar si hay nombres similares o si el OCR de la IA extrae mal el nombre.", "La creación puede fallar si los datos extraídos (ej. fechas) no tienen el formato correcto."], "evidence_files": ["src/pages/VendorCompliance.jsx"] }, { "name": "Vendor - Crear una factura", "entry": { "label": "Invoices", "route": "/Invoices" }, "steps": ["1. Usuario navega a `Invoices` y hace clic en 'Create Invoice'.", "2. Se abre `CreateInvoiceModal.jsx`.", "3. Se asume que el modal permite seleccionar una orden completada para pre-rellenar los datos.", "4. El usuario revisa los detalles y confirma.", "5. Se llama a `base44.entities.Invoice.create` para generar el nuevo registro."], "sdk_calls": ["base44.entities.Invoice.create"], "data_requirements": ["user.id", "user.company_name", "event list"], "known_failure_modes": ["UNKNOWN - Depende de la implementación de `CreateInvoiceModal`."], "evidence_files": ["src/pages/Invoices.jsx", "src/components/invoices/CreateInvoiceModal.jsx"] }, { "name": "Vendor - Ver su dashboard", "entry": { "label": "Home", "route": "/VendorDashboard" }, "steps": ["1. Al iniciar sesión, el usuario es dirigido a `VendorDashboard.jsx`.", "2. La página carga KPIs agregados: órdenes de hoy, órdenes en progreso, órdenes urgentes (RAPID) y staff asignado hoy.", "3. Muestra una tabla con las órdenes de hoy y mañana.", "4. El usuario puede hacer clic en una orden para ir al detalle (`EventDetail`) o iniciar una asignación (`SmartAssignModal`).", "5. También puede ver un carrusel de ingresos y paneles de 'Top Clients' y 'Top Performers'."], "sdk_calls": ["base44.auth.me", "base44.entities.Event.list", "base44.entities.Staff.list"], "data_requirements": ["user.id", "user.company_name"], "known_failure_modes": ["Las métricas pueden ser incorrectas si el filtrado de datos en el frontend es incompleto o no coincide con el backend."], "evidence_files": ["src/pages/VendorDashboard.jsx"] } ], "payload_contracts": [ { "entity": "Certification", "operations": { "create": { "payload_shape": ["employee_id", "employee_name", "certification_name", "certification_type", "certificate_number", "issuer", "issue_date", "expiry_date", "document_url", "vendor_id", "vendor_name", "owner", "expert_body", "validation_status", "ai_validation_result"], "evidence": ["src/pages/VendorCompliance.jsx > handleAddCertification, handleImportMatched"] } }, "missing_required_fields": [], "fields_sent_but_not_supported": [] }, { "entity": "Staff", "operations": { "create": { "payload_shape": "UNKNOWN", "evidence": ["src/pages/AddStaff.jsx (File not read, but workflow implies its existence and usage)"] } }, "missing_required_fields": ["Se asume que `vendor_id` es un campo requerido, y si el frontend no lo envía, la creación resultaría en un registro huérfano."], "fields_sent_but_not_supported": [] }, { "entity": "Event", "operations": { "update": { "payload_shape": ["assigned_staff", "shifts", "requested", "status"], "evidence": ["src/pages/VendorOrders.jsx > autoAssignMutation", "src/components/events/SmartAssignModal.jsx (assumed)"] } }, "missing_required_fields": [], "fields_sent_but_not_supported": [] }, { "entity": "Invoice", "operations": { "create": { "payload_shape": ["vendor_id", "business_name", "amount", "... (many others)"], "evidence": ["src/components/invoices/CreateInvoiceModal.jsx"] } }, "missing_required_fields": [], "fields_sent_but_not_supported": [] } ], "top_gaps": [ { "issue": "Inconsistencia en el Scoping de Datos del Vendor", "impact": "Alto. El vendor podría no ver todos sus datos. Por ejemplo, en `Invoices.jsx` se filtra por `user.vendor_id`, pero en `VendorOrders.jsx` y `VendorStaff.jsx` se filtra por `user.id`. Si estas propiedades no están alineadas, el vendor no verá sus facturas.", "evidence": ["src/pages/Invoices.jsx", "src/pages/VendorOrders.jsx", "src/api/krowSDK.js"] }, { "issue": "El filtrado de datos depende de múltiples claves, algunas frágiles", "impact": "Medio. El código usa `vendor_id`, `vendor_name`, y `created_by` para vincular datos al vendor. Si un vendor cambia su `company_name`, podría perder acceso a datos históricos que solo estaban vinculados por nombre.", "evidence": ["src/pages/VendorOrders.jsx", "src/pages/StaffDirectory.jsx"] }, { "issue": "Creación de Staff sin garantía de asignación de `vendor_id`", "impact": "Alto. La UI permite a un vendor ir a `AddStaff`, pero no se ha verificado si el payload de creación inyecta de forma forzosa el `vendor_id` del vendor logueado. Si no lo hace, se pueden crear empleados 'huérfanos' no asociados a ningún vendor.", "evidence": ["src/pages/AddStaff.jsx (existence implied)", "src/pages/StaffDirectory.jsx"] }, { "issue": "Permisos de edición de eventos no claros para vendors", "impact": "Bajo. El vendor puede acceder a la ruta `EditEvent`, pero no está claro qué campos del formulario puede editar. Esto puede causar confusión o errores si intenta modificar un campo restringido.", "evidence": ["src/pages/EditEvent.jsx"] }, { "issue": "Feature de 'Scheduler' incompleta", "impact": "Bajo. La página de órdenes (`VendorOrders.jsx`) tiene un botón para una vista de 'scheduler', pero la funcionalidad no está implementada, llevando a una UI incompleta.", "evidence": ["src/pages/VendorOrders.jsx"] }, { "issue": "El motor de 'Smart Assignment' puede no tener suficientes datos", "impact": "Medio. La función `autoFillShifts` en `VendorOrders` depende de `allStaff`, `vendorEvents` y `vendorRates`. Si alguna de estas listas está incompleta o es incorrecta, la asignación automática fallará o dará malos resultados.", "evidence": ["src/pages/VendorOrders.jsx", "src/components/scheduling/SmartAssignmentEngine.js"] } ], "next_verifications": [ "Leer el código de `AddStaff.jsx` y `CreateInvoiceModal.jsx` para documentar los `payload_contracts` de creación de forma precisa.", "Analizar el componente `EventForm.jsx` para determinar qué campos están realmente deshabilitados cuando `isVendor` es `true`.", "Revisar el schema de DataConnect (`.gql` files, si estuvieran disponibles) para verificar si existen constraints de `foreign key` que mitiguen el riesgo de datos huérfanos (ej. `Staff.vendor_id` -> `Users.id`).", "Aclarar con el equipo de producto si un vendor debería poder crear eventos o si solo se le asignan." ] }