other modifications days ago

This commit is contained in:
José Salazar
2025-12-26 15:14:51 -05:00
parent ec496fa1ba
commit 5136b1d6b5
56 changed files with 16364 additions and 3094 deletions

View File

@@ -644,7 +644,7 @@ export default function EventFormWizard({ event, onSubmit, onRapidSubmit, isSubm
<SelectValue placeholder="Choose vendor for this order" />
</SelectTrigger>
<SelectContent>
{vendors.filter(v => v.approval_status === 'approved').map((vendor) => (
{vendors.filter(v => v.approval_status === 'APPROVED').map((vendor) => (
<SelectItem key={vendor.id} value={vendor.id}>
{vendor.legal_name || vendor.doing_business_as}
{currentUserData?.preferred_vendor_id === vendor.id && (

View File

@@ -128,14 +128,14 @@ export default function Teams() {
teamName: teamName,
ownerId: user.id, // CRITICAL: Links team to THIS user only
ownerName: user.fullName || user.email,
ownerRole: userRole, // Tracks which layer this team belongs to
ownerRole: userRole.toUpperCase(), // Tracks which layer this team belongs to
//email: user.email,
//phone: user.phone || "",
//total_members: 0,
//active_members: 0,
//total_hubs: 0,
favoriteStaff: 0,
blockedStaff: 0,
//favoriteStaff: 0,
//blockedStaff: 0,
//departments: [], // Initialize with an empty array for departments
});
@@ -826,7 +826,7 @@ export default function Teams() {
vendor: "Vendor Team",
workforce: "Workforce Team"
};
return titles[userRole] || "Team";
return titles[userRole] || "TEAM";
};
const getIsolatedSubtitle = () => {
@@ -934,9 +934,9 @@ export default function Teams() {
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
</svg>
<strong>Isolated Team:</strong> You can only see and manage YOUR team members.
{userRole === 'vendor' && " Procurement teams are NOT visible to you."}
{userRole === 'procurement' && " Vendor teams are NOT visible to you."}
{userRole === 'operator' && " Other layer teams are NOT visible to you."}
{userRole === 'VENDOR' && " Procurement teams are NOT visible to you."}
{userRole === 'PROCUREMENT' && " Vendor teams are NOT visible to you."}
{userRole === 'OPERATOR' && " Other layer teams are NOT visible to you."}
</p>
</div>

View File

@@ -0,0 +1,249 @@
{
"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."
]
}

View File

@@ -0,0 +1,173 @@
{
"role": "VENDOR",
"role_detection": {
"summary": "La aplicación define el rol de un usuario mediante las propiedades 'user_role' o 'role' en el objeto 'user'. Este objeto se obtiene de `krowSDK.auth.me()`, que consulta la base de datos (DataConnect) usando el UID del usuario autenticado en Firebase. Adicionalmente, existe un mecanismo para desarrolladores que permite simular un rol usando `localStorage` (`krow_mock_user_role`), el cual tiene prioridad sobre el rol de la base de datos.",
"checks": [
{
"type": "Primary Check",
"condition": "user?.user_role || user?.role",
"files": ["src/pages/Layout.jsx", "src/pages/StaffDirectory.jsx", "src/pages/EditEvent.jsx"]
},
{
"type": "Developer Mock",
"condition": "localStorage.getItem('krow_mock_user_role')",
"files": ["src/api/krowSDK.js"]
}
],
"evidence": [
"src/api/krowSDK.js: La función `auth.me()` implementa esta lógica de unificación.",
"src/components/dev/RoleSwitcher.jsx: Este componente de desarrollo utiliza `localStorage` para cambiar de rol, demostrando el mecanismo de mock."
]
},
"menu_for_role": [
{
"label": "Home", "icon": "LayoutDashboard", "route": "/VendorDashboard", "page_component": "VendorDashboard", "page_file": "src/pages/VendorDashboard.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Orders", "icon": "FileText", "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", "icon": "DollarSign", "route": "/VendorRates", "page_component": "VendorRates", "page_file": "src/pages/VendorRates.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Invoices", "icon": "Clipboard", "route": "/Invoices", "page_component": "Invoices", "page_file": "src/pages/Invoices.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Schedule", "icon": "Calendar", "route": "/Schedule", "page_component": "Schedule", "page_file": "src/pages/Schedule.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Staff Availability", "icon": "Users", "route": "/StaffAvailability", "page_component": "StaffAvailability", "page_file": "src/pages/StaffAvailability.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Workforce", "icon": "Users", "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", "icon": "GraduationCap", "route": "/StaffOnboarding", "page_component": "StaffOnboarding", "page_file": "src/pages/StaffOnboarding.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Team", "icon": "UserCheck", "route": "/Teams", "page_component": "Teams", "page_file": "src/pages/Teams.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Task Board", "icon": "CheckSquare", "route": "/TaskBoard", "page_component": "TaskBoard", "page_file": "src/pages/TaskBoard.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Compliance", "icon": "Shield", "route": "/VendorCompliance", "page_component": "VendorCompliance", "page_file": "src/pages/VendorCompliance.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Communications", "icon": "MessageSquare", "route": "/Messages", "page_component": "Messages", "page_file": "src/pages/Messages.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Leads", "icon": "UserCheck", "route": "/Business", "page_component": "Business", "page_file": "src/pages/Business.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Business", "icon": "Briefcase", "route": "/Business", "page_component": "Business", "page_file": "src/pages/Business.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Reports", "icon": "BarChart3", "route": "/Reports", "page_component": "Reports", "page_file": "src/pages/Reports.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Audit Trail", "icon": "Activity", "route": "/ActivityLog", "page_component": "ActivityLog", "page_file": "src/pages/ActivityLog.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
},
{
"label": "Performance", "icon": "TrendingUp", "route": "/VendorPerformance", "page_component": "VendorPerformance", "page_file": "src/pages/VendorPerformance.jsx",
"visibility_condition": "userRole === 'vendor'", "defined_in": ["src/pages/Layout.jsx > roleNavigationMap"]
}
],
"router_all_routes": [
{ "route": "/", "component": "Home", "file": "src/pages/Home.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/login", "component": "Login", "file": "src/pages/Login.jsx", "guard": "PublicRoute", "role_constraints": "None" },
{ "route": "/Dashboard", "component": "Dashboard", "file": "src/pages/Dashboard.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/VendorDashboard", "component": "VendorDashboard", "file": "src/pages/VendorDashboard.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/VendorOrders", "component": "VendorOrders", "file": "src/pages/VendorOrders.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/VendorRates", "component": "VendorRates", "file": "src/pages/VendorRates.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/StaffDirectory", "component": "StaffDirectory", "file": "src/pages/StaffDirectory.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/Invoices", "component": "Invoices", "file": "src/pages/Invoices.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/EventDetail", "component": "EventDetail", "file": "src/pages/EventDetail.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/EditEvent", "component": "EditEvent", "file": "src/pages/EditEvent.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/AddStaff", "component": "AddStaff", "file": "src/pages/AddStaff.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/EditStaff", "component": "EditStaff", "file": "src/pages/EditStaff.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/InvoiceDetail", "component": "InvoiceDetail", "file": "src/pages/InvoiceDetail.jsx", "guard": "ProtectedRoute", "role_constraints": "Auth-only" },
{ "route": "/*", "component": "All others", "file": "src/pages/index.jsx", "guard": "ProtectedRoute", "role_constraints": "Todas las demás rutas siguen el mismo patrón: accesibles si se está autenticado, sin guard de rol a nivel de router." }
],
"navigation_discovered_routes": [
{ "from_component": "VendorDashboard", "from_action": "Click en icono 'View' de una orden en la tabla", "to_route": "/EventDetail?id=...", "evidence": ["src/pages/VendorDashboard.jsx > handleViewOrder"] },
{ "from_component": "VendorDashboard", "from_action": "Click en una orden 'RAPID' urgente", "to_route": "/EventDetail?id=...", "evidence": ["src/pages/VendorDashboard.jsx > handleRapidClick"] },
{ "from_component": "VendorOrders", "from_action": "Click en icono 'View' de una orden", "to_route": "/EventDetail?id=...", "evidence": ["src/pages/VendorOrders.jsx > Table onClick"] },
{ "from_component": "VendorOrders", "from_action": "Click en icono 'Edit' de una orden", "to_route": "/EditEvent?id=...", "evidence": ["src/pages/VendorOrders.jsx > Table onClick"] },
{ "from_component": "StaffDirectory", "from_action": "Click en el botón 'Add New Staff'", "to_route": "/AddStaff", "evidence": ["src/pages/StaffDirectory.jsx > PageHeader actions prop"] },
{ "from_component": "StaffDirectory", "from_action": "Click en el nombre de un empleado en la lista", "to_route": "/EditStaff?id=...", "evidence": ["src/pages/StaffDirectory.jsx > Link to={createPageUrl(...)}"] },
{ "from_component": "Invoices", "from_action": "Click en botón 'View' de una factura", "to_route": "/InvoiceDetail?id=...", "evidence": ["src/pages/Invoices.jsx > Table onClick"] }
],
"pages_inventory": {
"pages_in_menu_for_vendor": ["/VendorDashboard", "/VendorOrders", "/VendorRates", "/Invoices", "/Schedule", "/StaffAvailability", "/StaffDirectory", "/StaffOnboarding", "/Teams", "/TaskBoard", "/VendorCompliance", "/Messages", "/Business", "/Reports", "/ActivityLog", "/VendorPerformance"],
"pages_reachable_by_vendor": ["/VendorDashboard", "/VendorOrders", "/VendorRates", "/Invoices", "/Schedule", "/StaffAvailability", "/StaffDirectory", "/StaffOnboarding", "/Teams", "/TaskBoard", "/VendorCompliance", "/Messages", "/Business", "/Reports", "/ActivityLog", "/VendorPerformance", "/EventDetail", "/EditEvent", "/AddStaff", "/EditStaff", "/InvoiceDetail"],
"pages_url_direct_accessible": ["All routes defined in src/pages/index.jsx are technically accessible via URL if the user is authenticated, including admin-focused pages like /UserManagement or /EnterpriseManagement. The content within them would likely be empty or broken due to data scoping."]
},
"workflows": [
{ "name": "Vendor - Visualizar órdenes y navegar al detalle", "entry": {"type": "menu", "route": "/VendorOrders"}, "steps": ["Usuario navega a 'Orders' desde el menú.", "Se renderiza `VendorOrders.jsx` y se llama a `base44.entities.Event.list()`.", "Los datos se filtran en el frontend por `vendor_id === user.id`.", "El usuario ve la lista de sus órdenes.", "Hace clic en el icono 'View' de una orden.", "La app navega a `/EventDetail?id=...`."], "sdk_calls": ["base44.entities.Event.list"], "payload_exact": {}, "missing_fields": [], "extra_fields": [], "scoping": "`e.vendor_id === user?.id` OR `e.vendor_name === user?.company_name` OR `e.created_by === user?.email`" },
{ "name": "Vendor - Añadir nuevo miembro de staff", "entry": {"type": "internal", "route": "/StaffDirectory -> /AddStaff"}, "steps": ["Desde `/StaffDirectory`, el usuario hace clic en 'Add New Staff'.", "Navega a `/AddStaff`.", "Completa el formulario.", "Al guardar, se llama a `base44.entities.Staff.create`."], "sdk_calls": ["base44.entities.Staff.create"], "payload_exact": "UNKNOWN", "missing_fields": ["`vendor_id` es crítico aquí y debe ser inyectado por el frontend a partir del `user.id` del vendor logueado."], "extra_fields": [], "scoping": "El scoping se debe garantizar en la escritura (create), no solo en la lectura (list).", "evidence": ["src/pages/StaffDirectory.jsx"] },
{ "name": "Vendor - Editar un miembro de staff", "entry": {"type": "internal", "route": "/StaffDirectory -> /EditStaff"}, "steps": ["Desde `/StaffDirectory`, el usuario hace clic en el nombre de un empleado.", "Navega a `/EditStaff?id=...`.", "Modifica el formulario y guarda.", "Se llama a `base44.entities.Staff.update`."], "sdk_calls": ["base44.entities.Staff.update"], "payload_exact": "UNKNOWN", "missing_fields": [], "extra_fields": [], "scoping": "El `user.id` del staff es la clave para la actualización.", "evidence": ["src/pages/StaffDirectory.jsx"] },
{ "name": "Vendor - Crear factura para un evento", "entry": {"type": "menu", "route": "/Invoices"}, "steps": ["Usuario navega a 'Invoices'.", "Hace clic en 'Create Invoice'.", "Se abre el modal `CreateInvoiceModal`.", "El usuario selecciona un evento completado.", "El modal se pre-rellena con los datos del evento.", "Al confirmar, se llama a `base44.entities.Invoice.create`."], "sdk_calls": ["base44.entities.Invoice.create"], "payload_exact": {"vendor_id": "selectedEvent.vendor_id", "amount": "...", "business_name": "..."}, "missing_fields": [], "extra_fields": [], "scoping": "El `vendor_id` se toma del evento seleccionado.", "evidence": ["src/components/invoices/CreateInvoiceModal.jsx"] },
{ "name": "Vendor - Importar certificados de staff con IA", "entry": {"type": "menu", "route": "/VendorCompliance"}, "steps": ["Usuario navega a 'Compliance' y hace clic en 'Bulk Import'.", "Arrastra y suelta archivos PDF/JPG de certificados.", "`processBulkFiles` sube cada archivo (`UploadFile`) y lo envía a `InvokeLLM` para análisis.", "La IA extrae datos (nombre, fechas, etc.).", "El sistema intenta hacer match del nombre extraído con el staff del vendor (`findEmployeeByName`).", "El vendor revisa los matches y confirma la importación.", "Se llama a `base44.entities.Certification.create` por cada certificado validado."], "sdk_calls": ["base44.integrations.Core.UploadFile", "base44.integrations.Core.InvokeLLM", "base44.entities.Certification.create"], "payload_exact": {"employee_id": "...", "certification_name": "...", "expiry_date": "...", "vendor_id": "user.id", "ai_validation_result": "{...}"}, "missing_fields": [], "extra_fields": [], "scoping": "La creación de la certificación incluye `vendor_id: user?.id`.", "evidence": ["src/pages/VendorCompliance.jsx"] }
],
"payload_contracts": [
{
"entity": "Certification",
"operations": {
"create": {
"signature": "`createCertMutation.mutateAsync(certData)`",
"payload": {"employee_id": "string", "employee_name": "string", "certification_name": "string", "certification_type": "string", "certificate_number": "string", "issuer": "string", "issue_date": "date string", "expiry_date": "date string", "document_url": "string", "vendor_id": "string (user.id)", "vendor_name": "string (user.company_name)", "validation_status": "string", "ai_validation_result": "object" },
"optional_fields": ["certificate_number", "issuer", "issue_date", "document_url"],
"required_fields": ["employee_id", "certification_name", "expiry_date", "vendor_id"]
}
}, "fields_sent_but_not_supported": "UNKNOWN", "missing_fields_required_for_scoping": [], "evidence": ["src/pages/VendorCompliance.jsx > handleImportMatched"]
},
{
"entity": "Event",
"operations": {
"update": {
"signature": "`updateEventMutation.mutate({ id, data })`",
"payload": {"assigned_staff": "array", "shifts": "array", "requested": "number", "status": "string" },
"optional_fields": ["assigned_staff", "shifts", "requested", "status"],
"required_fields": []
}
}, "fields_sent_but_not_supported": "UNKNOWN", "missing_fields_required_for_scoping": [], "evidence": ["src/pages/VendorOrders.jsx > autoAssignMutation"]
},
{
"entity": "Staff",
"operations": { "create": { "signature": "UNKNOWN", "payload": "UNKNOWN" } },
"fields_sent_but_not_supported": "UNKNOWN", "missing_fields_required_for_scoping": ["`vendor_id` es esencial y debe ser añadido en el frontend antes de enviar a la API."], "evidence": ["src/pages/AddStaff.jsx (File not analyzed but existence is confirmed)"]
}
],
"top_gaps": [
{ "issue": "INCONSISTENCIA CRÍTICA en Scoping de Datos", "impact": "MUY ALTO. El vendor puede no ver datos que le pertenecen. `Invoices.jsx` filtra por `user.vendor_id` mientras que `VendorOrders.jsx` y `StaffDirectory.jsx` filtran por `user.id`. Si el objeto `user` no tiene la propiedad `vendor_id` o es diferente de `id`, las facturas no aparecerán. Esto es un bug severo.", "evidence": ["src/pages/Invoices.jsx", "src/pages/VendorOrders.jsx"] },
{ "issue": "Mecanismo de Scoping Frágil y Múltiple", "impact": "ALTO. El código recurrentemente usa `vendor_id === user.id || vendor_name === user.company_name || created_by === user.email`. Depender de `vendor_name` o `created_by` es muy arriesgado. Un cambio de nombre de la empresa o del email del creador podría 'desvincular' datos, haciéndolos inaccesibles.", "evidence": ["src/pages/StaffDirectory.jsx", "src/pages/VendorDashboard.jsx"] },
{ "issue": "Riesgo de Datos Huérfanos en Creación", "impact": "ALTO. La UI permite al vendor crear entidades como `Staff`. Sin embargo, no se ha verificado que el `vendor_id` se esté inyectando de forma garantizada en el payload de creación. Si el frontend no añade `vendor_id: user.id`, ese nuevo staff podría crearse sin dueño, siendo invisible para todos.", "evidence": ["Workflow 'Vendor - Añadir nuevo miembro de staff'"] },
{ "issue": "Seguridad de Rutas Basada solo en Ocultación de UI", "impact": "MEDIO. No hay guards de rol a nivel de router. Un vendor podría acceder a `/UserManagement` o `/EnterpriseManagement` si conoce la URL. Aunque la página probablemente no funcione por falta de datos, esto representa un modelo de seguridad débil.", "evidence": ["src/pages/index.jsx", "src/components/auth/ProtectedRoute.jsx"] },
{ "issue": "Componente de Desarrollo (`RoleSwitcher`) Presente", "impact": "BAJO. El componente `RoleSwitcher` que permite cambiar de rol arbitrariamente está presente en `Layout.jsx`. Aunque sea para desarrollo, su presencia en el código que podría llegar a producción es un riesgo de seguridad.", "evidence": ["src/pages/Layout.jsx", "src/components/dev/RoleSwitcher.jsx"] },
{ "issue": "Funcionalidad Incompleta Visible en la UI", "impact": "BAJO. Páginas como `VendorOrders` muestran un toggle para una vista 'Scheduler' que no está implementada. Lo mismo ocurre con `TaskBoard`. Esto degrada la experiencia de usuario.", "evidence": ["src/pages/VendorOrders.jsx"] },
{ "issue": "Permisos de Edición de Órdenes no Definidos", "impact": "BAJO. Un vendor puede navegar a `/EditEvent`, pero no está claro qué puede editar. Si la UI no deshabilita correctamente los campos restringidos, el vendor puede intentar guardar cambios que serán rechazados por el backend, causando frustración.", "evidence": ["src/pages/EditEvent.jsx"] }
],
"next_verifications": [
"AUDITAR `AddStaff.jsx`: Leer el código para encontrar el payload exacto de `base44.entities.Staff.create` y confirmar si `vendor_id` se está enviando.",
"AUDITAR `EditEvent.jsx` y su formulario `EventForm`: Determinar qué campos del formulario se deshabilitan cuando el usuario tiene el rol 'vendor'.",
"INVESTIGAR EL SCHEMA: Si es posible, revisar los archivos `.gql` de DataConnect para confirmar si hay `constraints` a nivel de base de datos que obliguen la presencia de `vendor_id` en `Staff`, `Events`, etc.",
"CLARIFICAR `user.vendor_id`: Investigar por qué el objeto `user` tendría una propiedad `vendor_id` separada de `id` y por qué `Invoices.jsx` es el único lugar que la usa."
]
}

View File

@@ -0,0 +1,218 @@
{
"role": "VENDOR",
"role_detection": {
"summary": "The application identifies a VENDOR user by checking a 'role' or 'user_role' property on the user object. A mock/override mechanism using localStorage also exists for development.",
"patterns": [
"user?.user_role === 'vendor'",
"user?.role === 'vendor'",
"user?.user_role || user?.role"
],
"files": [
"src/pages/Layout.jsx",
"src/pages/EditEvent.jsx",
"src/pages/StaffDirectory.jsx",
"src/components/events/ShiftCard.jsx",
"src/api/krowSDK.js"
],
"user_object_source": "The user object is a combination of the Firebase Auth user and a profile fetched from the backend via `base44.auth.me()` (which is an alias for `krowSDK.auth.me()`). The `krowSDK.auth.me` function calls `dcSdk.getUserById({ id: fbUser.uid })`."
},
"ownership_model": {
"vendor_id_meaning": "mixed",
"summary": "The meaning of 'vendor_id' is contextual. When a VENDOR user is logged in, their own Auth UID (`user.id`) is used as the 'vendor_id' to tag and query associated records like Staff and Orders. In other contexts (e.g., a client creating an order, or an admin managing vendors), 'vendor_id' refers to the UID of the separate vendor entity being referenced. This indicates that the User table serves as the master record for vendor entities.",
"evidence": [
"src/pages/VendorStaff.jsx: `staff.filter(s => s.vendor_id === user?.id)`",
"src/pages/VendorOrders.jsx: `allEvents.filter(e => e.vendor_id === user?.id)`",
"src/pages/VendorCompliance.jsx: `createCertMutation.mutate({ ... vendor_id: user?.id })`",
"src/components/events/EventFormWizard.jsx: Contains logic for a non-vendor user to select a vendor, where `vendor_id` is passed.",
"src/pages/Invoices.jsx: `invoices.filter(inv => inv.vendor_id === user?.vendor_id)`. This is an INCONSISTENCY, as it uses `user.vendor_id` instead of `user.id`. This could be a bug or a property specific to staff members of a vendor."
],
"current_workarounds_seen": [
"The code often checks multiple properties to link data to a vendor, suggesting a lack of a single, enforced source of truth. Pattern: `e.vendor_name === user?.company_name || e.vendor_id === user?.id || e.created_by === user?.email`.",
"This multi-key check (vendor_name, vendor_id, created_by) is a risk for data integrity and indicates there may not be a strict foreign key relationship enforced at the creation of all records."
],
"risks": [
"Data-scoping inconsistencies (`user.id` vs `user.vendor_id`) could lead to vendors not seeing all their data (e.g., invoices).",
"Relying on `vendor_name` or `user.email` for data filtering is brittle. A name change could orphan data.",
"UI allows staff/order creation, but it's unclear if `vendor_id` is always enforced, potentially creating un-owned records."
]
},
"pages": [
{
"page": "VendorDashboard",
"path": "src/pages/VendorDashboard.jsx",
"route": "/vendor-dashboard",
"purpose": "Provides a high-level, customizable overview of KPIs, recent orders, and quick actions for the vendor.",
"actions": ["View KPIs (orders, staff)", "View list of today/tomorrow's orders", "Navigate to EventDetail", "Open SmartAssignModal", "Navigate to StaffDirectory", "Customize dashboard widget layout"],
"sdk_calls": ["base44.auth.me", "base44.entities.Event.list", "base44.entities.Staff.list", "base44.auth.updateMe"]
},
{
"page": "VendorOrders",
"path": "src/pages/VendorOrders.jsx",
"route": "/vendor-orders",
"purpose": "Primary interface for a vendor to manage all their assigned orders.",
"actions": ["Search and filter orders", "View orders by status (upcoming, active, conflicts)", "Navigate to EventDetail", "Navigate to EditEvent", "Trigger SmartAssignModal", "Detects scheduling conflicts"],
"sdk_calls": ["base44.auth.me", "base44.entities.Event.list", "base44.entities.Staff.list", "base44.entities.VendorRate.list", "base44.entities.Event.update"]
},
{
"page": "StaffDirectory",
"path": "src/pages/StaffDirectory.jsx",
"route": "/staff-directory",
"purpose": "Allows a vendor to view and manage their own workforce.",
"actions": ["View list/grid of staff members", "Filter staff", "Navigate to AddStaff page", "Navigate to EditStaff page"],
"sdk_calls": ["base44.auth.me", "base44.entities.Staff.list", "base44.entities.Event.list"]
},
{
"page": "VendorRates",
"path": "src/pages/VendorRates.jsx",
"route": "/vendor-rates",
"purpose": "Allows a vendor to manage their own pricing structures and custom rate cards for clients.",
"actions": ["View approved enterprise rates", "Create/edit/delete custom rate cards", "Perform AI-powered price analysis", "Export rates to CSV"],
"sdk_calls": ["base44.auth.me", "base44.entities.VendorRate.list", "base44.integrations.Core.InvokeLLM"]
},
{
"page": "VendorCompliance",
"path": "src/pages/VendorCompliance.jsx",
"route": "/vendor-compliance",
"purpose": "Allows a vendor to manage and track certifications for their workforce.",
"actions": ["View dashboard of certificate statuses", "Add new certifications for employees", "Bulk import certificates with AI data extraction", "View certificate documents"],
"sdk_calls": ["base44.auth.me", "base44.entities.Certification.list", "base44.entities.Staff.list", "base44.entities.Certification.create", "base44.integrations.Core.UploadFile", "base44.integrations.Core.InvokeLLM"]
},
{
"page": "Invoices",
"path": "src/pages/Invoices.jsx",
"route": "/invoices",
"purpose": "Allows a vendor to view their invoices and create new ones.",
"actions": ["View list of invoices", "Filter by status", "View invoice details", "Create new invoice from modal"],
"sdk_calls": ["base44.auth.me", "base44.entities.Invoice.list", "base44.entities.Invoice.update"]
}
],
"workflows": [
{
"name": "Vendor Views Orders",
"steps": [
"1. Vendor logs in and is redirected to the dashboard.",
"2. User navigates to 'Orders' tab in the main menu.",
"3. The `VendorOrders.jsx` page loads.",
"4. A `useQuery` hook calls `base44.entities.Event.list('-date')` to fetch all events.",
"5. A `useMemo` hook filters the events where `e.vendor_id === user.id` (among other fallback conditions).",
"6. The component renders the filtered list of orders in a table, showing status, dates, and staff counts."
]
},
{
"name": "Vendor Edits an Order/Event",
"steps": [
"1. From the `VendorOrders` page, the vendor clicks the 'Edit' icon on an order row.",
"2. The app navigates to `EditEvent?id={event.id}`.",
"3. The `EditEvent.jsx` page loads.",
"4. A check `const isVendor = user?.user_role === 'vendor'` is performed.",
"5. The UI is likely restricted based on this check (e.g., a vendor cannot change the client or business name). The exact restrictions are UNKNOWN without deeper analysis of the `EventForm` component.",
"6. Vendor modifies allowed fields (e.g., notes, internal fields).",
"7. Vendor clicks 'Save'. The form's `onSubmit` handler calls `base44.entities.Event.update(id, data)`."
]
},
{
"name": "Vendor Adds a New Staff Member",
"steps": [
"1. Vendor navigates to the 'Workforce' page (`StaffDirectory.jsx`).",
"2. The page title confirms they are in their scoped view: 'My Staff Directory'.",
"3. Vendor clicks the 'Add New Staff' button.",
"4. The app navigates to the `AddStaff.jsx` page.",
"5. Vendor fills out the staff member's details.",
"6. On save, the `createStaff` mutation is called. It is CRITICAL that this call includes `{ vendor_id: user.id, vendor_name: user.company_name }` to correctly associate the new staff member. This is ASSUMED based on scoping logic, but not explicitly verified in `AddStaff.jsx`.",
"7. The user is redirected back to the staff directory and sees the new member."
]
},
{
"name": "Vendor Assigns Staff to Order using Smart Assign",
"steps": [
"1. From `VendorDashboard` or `VendorOrders`, the vendor finds an order that is not fully staffed.",
"2. The vendor clicks the 'Smart Assign' (`UserCheck`) icon.",
"3. The `SmartAssignModal.jsx` component is opened, passing in the event object.",
"4. The modal likely displays a list of available and suitable staff from the vendor's workforce.",
"5. The vendor selects staff members from the list.",
"6. On 'Confirm', the modal's logic calls `base44.entities.Event.update` with the updated `assigned_staff` array for that event.",
"7. The table on the `VendorOrders` page refreshes to show the new `ASSIGNED` count."
]
},
{
"name": "Vendor Creates a Custom Rate Card",
"steps": [
"1. Vendor navigates to 'Service Rates' (`VendorRates.jsx`).",
"2. The `VendorCompanyPricebookView` component is rendered.",
"3. Vendor clicks the 'New Card' button in the 'Custom Rate Cards' section.",
"4. The `RateCardModal.jsx` component appears.",
"5. Vendor gives the rate card a name (e.g., 'Special Client ABC Rate') and defines the rates for various roles.",
"6. On save, the new rate card is added to the component's state and becomes selectable in the UI."
]
},
{
"name": "Vendor Manages Staff Certifications with AI",
"steps": [
"1. Vendor navigates to 'Compliance' (`VendorCompliance.jsx`).",
"2. Vendor decides to add a new certification and clicks 'Add Certification'.",
"3. In the dialog, instead of manually typing, vendor clicks 'Upload Document'.",
"4. An `<input type='file'>` is triggered. Vendor selects a PDF of a ServSafe certificate.",
"5. `handleFileUpload` is called, which uploads the file via `base44.integrations.Core.UploadFile`.",
"6. On success, `validateCertificateWithAI` is called, passing the file URL to `base44.integrations.Core.InvokeLLM`.",
"7. The LLM returns a JSON object with extracted data (name, expiry date, etc.).",
"8. The form fields in the dialog are auto-populated with the AI's response.",
"9. Vendor selects the employee(s) this applies to and clicks 'Add Certification'.",
"10. `createCertMutation` is called, saving the record with the AI validation data attached."
]
},
{
"name": "Vendor Bulk Imports Certifications",
"steps": [
"1. From `VendorCompliance.jsx`, vendor clicks 'Bulk Import'.",
"2. `DialogBulkImport.jsx` opens, showing a drag-and-drop area.",
"3. Vendor drops 10 PDF certificate files onto the area.",
"4. `processBulkFiles` is triggered. It iterates through each file.",
"5. For each file, it uploads it, then calls the AI to extract holder name and other data.",
"6. It then calls `findEmployeeByName` to fuzzy-match the extracted name to an employee in the `staff` list.",
"7. The UI updates with a table showing each file, the extracted name, the matched employee, and a match confidence score.",
"8. For any 'unmatched' certs, the vendor can use a dropdown to manually select the correct employee.",
"9. Vendor clicks 'Import Matched'. The system calls `createCertMutation` for each certificate that has a matched employee."
]
},
{
"name": "Vendor Creates an Invoice",
"steps": [
"1. Vendor navigates to 'Invoices' (`Invoices.jsx`).",
"2. Vendor clicks 'Create Invoice'.",
"3. The `CreateInvoiceModal.jsx` opens.",
"4. The vendor can likely choose a completed event/order to generate the invoice from.",
"5. The modal is pre-populated with data from the selected event (e.g., client name, amount). This is an ASSUMPTION based on component name.",
"6. Vendor confirms the details and clicks 'Create'.",
"7. The `createInvoice` mutation is called, and the new invoice appears in the main table."
]
}
],
"gaps": [
{
"area": "Event Creation",
"description": "It is unclear if a VENDOR is ever allowed to create an event. The navigation in `Layout.jsx` does not include a 'Create Event' link for vendors. However, `EditEvent.jsx` has an `isVendor` check, which implies they might interact with the form. The system seems designed for vendors to be assigned to events, not to create them.",
"files": ["src/pages/Layout.jsx", "src/pages/CreateEvent.jsx"]
},
{
"area": "Data Creation Ownership",
"description": "While scoping on reads is clear (`vendor_id === user.id`), the enforcement during writes is not. For example, when a vendor creates a staff member via `AddStaff.jsx`, is the `vendor_id` automatically and correctly assigned on the backend or is the form responsible? This is a potential source of orphaned data if not handled robustly.",
"files": ["src/pages/AddStaff.jsx"]
},
{
"area": "Inconsistent Data Scoping",
"description": "The `Invoices.jsx` page filters using `user.vendor_id`, while most other pages use `user.id`. This is a significant inconsistency that could cause a vendor to not see their invoices if the `user.vendor_id` property is not populated correctly on their user object.",
"files": ["src/pages/Invoices.jsx", "src/pages/VendorOrders.jsx", "src/api/krowSDK.js"]
},
{
"area": "Scheduler View",
"description": "The `VendorOrders.jsx` page has a UI toggle for a 'scheduler' view, but the implementation for it is missing. This is a planned but incomplete feature.",
"files": ["src/pages/VendorOrders.jsx"]
}
],
"next_verifications": [
"Read the code for `AddStaff.jsx` to confirm that `vendor_id` is being set to `user.id` when a vendor creates a new staff record.",
"Investigate the `useQuery` hook for invoices in `Invoices.jsx` and the `user` object to understand why it uses `user.vendor_id` and determine if it's a bug.",
"Examine the `EventForm.jsx` or `EventFormWizard.jsx` component to understand exactly what fields are disabled or hidden when `isVendor` is true.",
"Check the `dataconnect/schema` to see if there is a foreign key constraint between `Staff.vendor_id` and `Users.id`."
]
}

View File

@@ -0,0 +1,60 @@
Label menú Type Description Rute Archivo Menu Vendor Cómo se accede
Home PAGE /VendorDashboard src/pages/VendorDashboard.jsx Sí Menú (roleNavigationMap)
Home Customize OPERATION Customize vendor dashboard No Button Dashboard
Orders PAGE /VendorOrders src/pages/VendorOrders.jsx Sí Menú (roleNavigationMap)
Orders List Events OPERATION No Desde VendorOrders
Orders Event Detail PAGE /EventDetail src/pages/EventDetail.jsx No Table / Actions --> Mouse Over (View)
Orders Edit Event PAGE /EditEvent src/pages/EditEvent.jsx No Table / Actions --> Mouse Over (Edit)
Orders Update Event (ALL) OPERATION No Table / Actions --> Mouse Over (Edit)--> Edit Event
Orders Smart Assign COMPONENT src/components/events/SmartAssignModal.jsx No Table / Actions --> Mouse Over (Smart Assign)
Orders Update Event (ONLY SHIFT) OPERATION No Table / Actions --> Mouse Over (Smart Assign) --> Assign Staff
Service Rates PAGE /VendorRates src/pages/VendorRates.jsx Sí Menú (roleNavigationMap)
Service Rates AI Price Check OPERATION No Button: AI Price Check
Service Rates New Card COMPONENT src/components/rates/RateCardModal.jsx No Button: + New Card
Service Rates Create Card OPERATION No New Card --> Create Custom Rate Card
Service Rates Edit Card OPERATION No Custom Rate Cards --> Mouse Over a card (Rename)
Service Rates Delete Card OPERATION No Custom Rate Cards --> Mouse Over a card (Delete)
Invoices PAGE /Invoices src/pages/Invoices.jsx Sí Menú (roleNavigationMap)
Invoices List Invoice OPERATION No Desde Invoices
Invoices Invoice Detail PAGE /InvoiceDetail src/pages/InvoiceDetail.jsx No Table / Actions --> View
Invoices Create Invoice PAGE /Invoiceeditor No Button: + Create Invoice --> Open Invoice Editor
Invoices Create Invoice OPERATION No Button: Create Invoice
Schedule PAGE /Schedule src/pages/Schedule.jsx Sí Menú (roleNavigationMap)
Schedule View Schedule OPERATION Visualiza un calendario con eventos/shifts. No Desde Schedule
Staff Availability PAGE /StaffAvailability src/pages/StaffAvailability.jsx Sí Menú (roleNavigationMap)
Staff Availability View Staff Availability OPERATION Visualiza la disponibilidad y utilización del staff. No Desde Staff Availability
Workforce PAGE /StaffDirectory src/pages/StaffDirectory.jsx Sí Menú (roleNavigationMap)
Workforce List Staff OPERATION Visualiza la lista de miembros de staff del vendor. No Desde StaffDirectory
Workforce Add Staff OPERATION Añade un nuevo miembro de staff. No Button: Add New Staff
Workforce Edit Staff OPERATION Edita un miembro de staff existente. No Click en un miembro de staff en la lista
Onboard Staff PAGE /StaffOnboarding src/pages/StaffOnboarding.jsx Sí Menú (roleNavigationMap)
Onboard Staff Initiate Onboarding OPERATION Inicia un proceso de onboarding de 4 pasos para nuevo staff. No Desde StaffOnboarding
Onboard Staff Create Staff OPERATION Crea un nuevo registro de staff al completar el onboarding. No Último paso del Wizard: Botón 'Complete'
Onboard Staff Navigate to WorkforceDashboard OPERATION Redirige al dashboard del staff tras completar el onboarding. No Automático tras completar
Team PAGE /Teams src/pages/Teams.jsx Sí Menú (roleNavigationMap)
Team View Team OPERATION Visualiza el equipo aislado del vendor, miembros, hubs, etc. No Desde Teams
Team Invite Member OPERATION Abre un modal para invitar un nuevo miembro al equipo. No Button: Invite Member
Team Create Hub OPERATION Abre un modal para crear un nuevo hub (ubicación). No Button: Create Hub
Team Edit Member OPERATION Abre un modal para editar los detalles de un miembro. No Botón 'Edit' en la tarjeta/fila del miembro
Team Deactivate/Activate Member OPERATION Cambia el estado de un miembro del equipo. No Botones en la tarjeta/fila del miembro
Team Manage Favorites OPERATION Añade/quita staff de la lista de favoritos del equipo. No Pestaña 'Favorites'
Team Manage Blocked OPERATION Añade/quita staff de la lista de bloqueados del equipo. No Pestaña 'Blocked'
Task Board PAGE /TaskBoard src/pages/TaskBoard.jsx Sí Menú (roleNavigationMap)
Task Board List Tasks OPERATION Visualiza las tareas del equipo del vendor en un tablero Kanban. No Desde TaskBoard
Task Board Create Task OPERATION Abre un diálogo para crear una nueva tarea. No Button: Create Task
Task Board Update Task Status OPERATION Cambia el estado de una tarea arrastrándola entre columnas. No Drag & Drop entre columnas
Task Board View Task Details OPERATION Abre un modal para ver/editar detalles de una tarea. No Click en una tarjeta de tarea
Compliance PAGE /VendorCompliance src/pages/VendorCompliance.jsx Sí Menú (roleNavigationMap)
Compliance View Certifications OPERATION Visualiza las certificaciones del staff, filtrando por estado. No Desde VendorCompliance
Compliance Add Certification OPERATION Abre un diálogo para añadir una certificación individual. No Button: Add Certification
Compliance Bulk Import Certifications OPERATION Abre un diálogo para importar múltiples certificaciones en bloque. No Button: Bulk Import
Communications PAGE /Messages src/pages/Messages.jsx Sí Menú (roleNavigationMap)
Communications View Conversations OPERATION Visualiza una lista de conversaciones existentes. No Desde Messages
Communications View Message Thread OPERATION Visualiza los mensajes de una conversación seleccionada. No Seleccionar una conversación de la lista
Communications Send Message OPERATION Envía un mensaje en la conversación activa. No Componente MessageInput
Communications Create New Conversation OPERATION Abre un diálogo para iniciar una nueva conversación (individual o grupal). No Button: New Conversation
Leads PAGE /Business src/pages/Business.jsx Sí Menú (roleNavigationMap)
Leads List Businesses OPERATION Visualiza un directorio de clientes/partners de negocio. No Desde Business
Leads Add Business OPERATION Abre un modal para crear un nuevo cliente/partner de negocio. No Button: Add Business
Leads Edit Business OPERATION Edita un cliente/partner de negocio existente. No Click en 'Edit' en una BusinessCard