diff --git a/.gitignore b/.gitignore index c3fa53545..cee79ea28 100644 --- a/.gitignore +++ b/.gitignore @@ -54,9 +54,8 @@ vite.config.d.ts # Outputs de auditoria/triage — gerados localmente, não devem versionar triage-edge-typecheck.json triage-*.json +audit/db-frontend-coverage.json +audit/internal-schema.tsv # Generated by e2e/theme-validation.spec.ts afterAll → scripts/generate-theme-report.mjs theme-validation-output/ - -# Generated by scripts/generate-health.mjs -public/api/health.json diff --git a/audit/db-frontend-coverage.json b/audit/db-frontend-coverage.json deleted file mode 100644 index dbff2a4ca..000000000 --- a/audit/db-frontend-coverage.json +++ /dev/null @@ -1,9669 +0,0 @@ -{ - "generatedAt": "2026-05-16T00:23:27.064Z", - "reports": [ - { - "label": "BD Interno (app)", - "tables": [ - { - "table": "access_security_settings", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "ip_whitelist_enabled", - "type": "boolean", - "status": "READ" - }, - { - "name": "city_whitelist_enabled", - "type": "boolean", - "status": "READ" - }, - { - "name": "block_unknown_locations", - "type": "boolean", - "status": "READ" - }, - { - "name": "max_failed_attempts", - "type": "integer", - "status": "READ" - }, - { - "name": "lockout_duration_minutes", - "type": "integer", - "status": "READ" - }, - { - "name": "strict_access_mode", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 83, - "orphanCount": 1, - "totalCount": 9 - }, - { - "table": "admin_audit_log", - "module": "Auditoria", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "action", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "resource_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "resource_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "details", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "request_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "started_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "finished_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "payload_summary", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "source", - "type": "text", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 16 - }, - { - "table": "admin_audit_log_old", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2025m12", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2026m01", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2026m02", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2026m03", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2026m04", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2026m05", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_audit_log_y2026m06", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "admin_settings", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "key", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "value", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "updated_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "ai_insights_cache", - "module": "IA & Flow", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "function_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "cache_key", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "payload", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "model", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "tokens_input", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "tokens_output", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "ai_usage_events", - "module": "IA & Flow", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "function_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "event_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "ai_usage_logs", - "module": "IA & Flow", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "function_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "model", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "input_tokens", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "output_tokens", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "total_tokens", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "estimated_cost_usd", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "error_message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 13 - }, - { - "table": "ai_usage_quotas", - "module": "IA & Flow", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "role", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "monthly_limit", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "is_unlimited", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "app_vitals", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "metric_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "metric_value", - "type": "numeric", - "status": "ORPHAN" - }, - { - "name": "rating", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "request_id", - "type": "text", - "status": "READ" - }, - { - "name": "page_url", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 43, - "orphanCount": 4, - "totalCount": 9 - }, - { - "table": "art_file_attachments", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "mockup_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "quote_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "file_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "file_path", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "original_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "mime_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "file_size_bytes", - "type": "bigint", - "status": "READ+WRITE" - }, - { - "name": "file_extension", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 13 - }, - { - "table": "audit_logs", - "module": "Auditoria", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "event_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "endpoint", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "identifier", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "auth_login_attempts", - "module": "Segurança", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "email", - "type": "text", - "status": "READ" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ" - }, - { - "name": "success", - "type": "boolean", - "status": "READ" - }, - { - "name": "failure_reason", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 80, - "orphanCount": 1, - "totalCount": 7 - }, - { - "table": "bot_detection_log", - "module": "Segurança", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "endpoint", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "detection_reason", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "request_count", - "type": "integer", - "status": "READ" - }, - { - "name": "blocked", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "cart_templates", - "module": "Kits & Carrinhos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "items", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "category_icons", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "category_name", - "type": "text", - "status": "READ" - }, - { - "name": "icon", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "collection_item_reactions", - "module": "Coleções", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "collection_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "item_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "anon_id", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "emoji", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "ip_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 33, - "orphanCount": 4, - "totalCount": 8 - }, - { - "table": "collection_items", - "module": "Coleções", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "collection_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color_hex", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "thumbnail_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "price_at_save", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "added_at", - "type": "timestamp with time zone", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "collection_items_trash", - "module": "Coleções", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "original_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "collection_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color_hex", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "thumbnail_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "price_at_save", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "deleted_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - } - ], - "coverage": 91, - "orphanCount": 1, - "totalCount": 13 - }, - { - "table": "collections", - "module": "Coleções", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_featured", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "icon_color", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "icon", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "share_token", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "share_expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "is_public", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "is_deleted", - "type": "boolean", - "status": "ORPHAN" - } - ], - "coverage": 92, - "orphanCount": 1, - "totalCount": 15 - }, - { - "table": "comparison_reactions", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "comparison_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "item_index", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "emoji", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "anon_id", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "ip_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 17, - "orphanCount": 5, - "totalCount": 8 - }, - { - "table": "component_media", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "component_id", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "media_type", - "type": "text", - "status": "READ" - }, - { - "name": "url", - "type": "text", - "status": "READ" - }, - { - "name": "title", - "type": "text", - "status": "READ" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ" - }, - { - "name": "is_cover", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 71, - "orphanCount": 2, - "totalCount": 10 - }, - { - "table": "connection_test_history", - "module": "Webhooks & Conexões", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "connection_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "tested_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "success", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "latency_ms", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "status_code", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "error_message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "triggered_by", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "error_kind", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "request_method", - "type": "text", - "status": "READ" - }, - { - "name": "request_url", - "type": "text", - "status": "READ" - }, - { - "name": "response_headers", - "type": "jsonb", - "status": "READ" - }, - { - "name": "response_body", - "type": "text", - "status": "READ" - }, - { - "name": "dns_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "tcp_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "tls_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "ttfb_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "download_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "triggered_by_user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "attempts", - "type": "smallint", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 21 - }, - { - "table": "conversation_audit_logs", - "module": "Auditoria", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "session_id", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "started_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "ended_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "total_tokens_estimated", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "client_info", - "type": "jsonb", - "status": "ORPHAN" - } - ], - "coverage": 50, - "orphanCount": 4, - "totalCount": 9 - }, - { - "table": "conversation_delivery_status", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "event_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "error_details", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 33, - "orphanCount": 2, - "totalCount": 5 - }, - { - "table": "conversation_event_history", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "conversation_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "role", - "type": "text", - "status": "READ" - }, - { - "name": "event_type", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "content", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "media_url", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "media_metadata", - "type": "jsonb", - "status": "ORPHAN" - }, - { - "name": "tokens_estimated", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "request_id", - "type": "uuid", - "status": "READ" - } - ], - "coverage": 38, - "orphanCount": 5, - "totalCount": 10 - }, - { - "table": "custom_kits", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "box_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "items_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "personalization_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "kit_quantity", - "type": "integer", - "status": "READ" - }, - { - "name": "box_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "items_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "personalization_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "total_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "volume_usage_percent", - "type": "numeric", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "kit_type", - "type": "text", - "status": "READ" - }, - { - "name": "color", - "type": "text", - "status": "READ" - }, - { - "name": "tag", - "type": "text", - "status": "READ" - }, - { - "name": "icon", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "is_favorite", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "last_used_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "is_pinned", - "type": "boolean", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 23 - }, - { - "table": "discount_approval_requests", - "module": "Preços & Descontos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "requested_discount_percent", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "max_allowed_percent", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "admin_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "admin_notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "seller_notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "responded_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 12 - }, - { - "table": "e2e_cleanup_audit", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "email", - "type": "text", - "status": "READ" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "dry_run", - "type": "boolean", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "reason", - "type": "text", - "status": "READ" - }, - { - "name": "ip", - "type": "text", - "status": "READ" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "total_deleted", - "type": "integer", - "status": "READ" - }, - { - "name": "deleted_by_table", - "type": "jsonb", - "status": "READ" - }, - { - "name": "errors", - "type": "jsonb", - "status": "READ" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "seller_scope", - "type": "text", - "status": "READ" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "name_filter_prefix", - "type": "text", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 16 - }, - { - "table": "e2e_cleanup_rate_limit", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "key", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "count", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "window_start", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 0, - "orphanCount": 3, - "totalCount": 4 - }, - { - "table": "expert_conversations", - "module": "IA & Flow", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "title", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "expert_messages", - "module": "IA & Flow", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "conversation_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "role", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "content", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 5 - }, - { - "table": "external_connections", - "module": "Integrações", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ" - }, - { - "name": "config", - "type": "jsonb", - "status": "READ" - }, - { - "name": "secret_refs", - "type": "ARRAY", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "last_test_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "last_test_ok", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "last_test_message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "last_latency_ms", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "env_key", - "type": "text", - "status": "READ" - }, - { - "name": "auto_test_enabled", - "type": "boolean", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 15 - }, - { - "table": "external_connections_sync_log", - "module": "Integrações", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "ran_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "triggered_by_user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "triggered_by_secret_name", - "type": "text", - "status": "READ" - }, - { - "name": "trigger_op", - "type": "text", - "status": "READ" - }, - { - "name": "processed", - "type": "integer", - "status": "READ" - }, - { - "name": "created_count", - "type": "integer", - "status": "READ" - }, - { - "name": "updated_count", - "type": "integer", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "error_message", - "type": "text", - "status": "READ" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "details", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 13 - }, - { - "table": "favorite_item_reactions", - "module": "Favoritos", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "item_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "list_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "anon_id", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "emoji", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "ip_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 33, - "orphanCount": 4, - "totalCount": 8 - }, - { - "table": "favorite_items", - "module": "Favoritos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "list_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_info", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "note", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "price_at_save", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "position", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "added_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "favorite_items_trash", - "module": "Favoritos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "original_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "list_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_info", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "note", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "price_at_save", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "deleted_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - } - ], - "coverage": 89, - "orphanCount": 1, - "totalCount": 11 - }, - { - "table": "favorite_lists", - "module": "Favoritos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "icon", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_default", - "type": "boolean", - "status": "READ" - }, - { - "name": "is_archived", - "type": "boolean", - "status": "READ" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "shared_token", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "shared_expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "position", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 15 - }, - { - "table": "favorites", - "module": "Favoritos", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "variant_info", - "type": "jsonb", - "status": "ORPHAN" - }, - { - "name": "added_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "is_deleted", - "type": "boolean", - "status": "ORPHAN" - } - ], - "coverage": 40, - "orphanCount": 3, - "totalCount": 7 - }, - { - "table": "file_scan_logs", - "module": "Segurança", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "bucket", - "type": "character varying", - "status": "READ" - }, - { - "name": "path", - "type": "text", - "status": "READ" - }, - { - "name": "hash", - "type": "character varying", - "status": "READ" - }, - { - "name": "scan_result", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "status_code", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "follow_up_reminders", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_id", - "type": "text", - "status": "READ" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "reminder_type", - "type": "text", - "status": "READ" - }, - { - "name": "scheduled_for", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "is_sent", - "type": "boolean", - "status": "READ" - }, - { - "name": "sent_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "title", - "type": "text", - "status": "READ" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "is_completed", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "completed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 80, - "orphanCount": 2, - "totalCount": 12 - }, - { - "table": "generated_mockups", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_sku", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "technique_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "technique_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "logo_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "mockup_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "layout_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "position_x", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "position_y", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "logo_width_cm", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "logo_height_cm", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "location_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "colors_count", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "annotations", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 20 - }, - { - "table": "geo_allowed_countries", - "module": "Segurança", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "country_code", - "type": "character", - "status": "READ+WRITE" - }, - { - "name": "country_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "hardening_health_snapshots", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "snapshot_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "score", - "type": "integer", - "status": "READ" - }, - { - "name": "max_score", - "type": "integer", - "status": "READ" - }, - { - "name": "failures", - "type": "ARRAY", - "status": "READ" - }, - { - "name": "details", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "inbound_webhook_endpoints", - "module": "Webhooks & Conexões", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "slug", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "source_system", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "hmac_secret_ref", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "allowed_events", - "type": "ARRAY", - "status": "ORPHAN" - }, - { - "name": "active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "last_received_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "total_received", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "total_invalid", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 91, - "orphanCount": 1, - "totalCount": 14 - }, - { - "table": "inbound_webhook_events", - "module": "Webhooks & Conexões", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "endpoint_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "event_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "payload", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "signature_valid", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "processed", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "error", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "source_ip", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "received_at", - "type": "timestamp with time zone", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "integration_credentials", - "module": "Integrações", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "secret_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "secret_value", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "masked_suffix", - "type": "text", - "status": "READ" - }, - { - "name": "length", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "updated_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "ip_access_control", - "module": "Segurança", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "list_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "reason", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "kit_collaborators", - "module": "Kits & Carrinhos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "kit_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "permission", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "invited_by", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "invited_email", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 80, - "orphanCount": 1, - "totalCount": 8 - }, - { - "table": "kit_comments", - "module": "Kits & Carrinhos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "kit_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "author_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "parent_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "item_anchor", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "body", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "resolved", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "kit_share_tokens", - "module": "Kits & Carrinhos", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "kit_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "token", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "client_name", - "type": "text", - "status": "READ" - }, - { - "name": "client_email", - "type": "text", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "viewed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 63, - "orphanCount": 3, - "totalCount": 11 - }, - { - "table": "kit_templates", - "module": "Kits & Carrinhos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "category", - "type": "text", - "status": "READ" - }, - { - "name": "color", - "type": "text", - "status": "READ" - }, - { - "name": "icon", - "type": "text", - "status": "READ" - }, - { - "name": "tag", - "type": "text", - "status": "READ" - }, - { - "name": "cover_image_url", - "type": "text", - "status": "READ" - }, - { - "name": "box_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "items_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "personalization_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "total_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "volume_usage_percent", - "type": "numeric", - "status": "READ" - }, - { - "name": "usage_count", - "type": "integer", - "status": "READ" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 18 - }, - { - "table": "kit_variants", - "module": "Kits & Carrinhos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "kit_master_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "label", - "type": "text", - "status": "READ" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ" - }, - { - "name": "box_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "items_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "personalization_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "kit_quantity", - "type": "integer", - "status": "READ" - }, - { - "name": "total_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "login_attempts", - "module": "Segurança", - "rows": 4, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "email", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "success", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "failure_reason", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "magic_up_brand_kits", - "module": "Magic Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ" - }, - { - "name": "client_name", - "type": "text", - "status": "READ" - }, - { - "name": "logo_urls", - "type": "jsonb", - "status": "READ" - }, - { - "name": "primary_color", - "type": "text", - "status": "READ" - }, - { - "name": "secondary_color", - "type": "text", - "status": "READ" - }, - { - "name": "tone_of_voice", - "type": "text", - "status": "READ" - }, - { - "name": "visual_style", - "type": "text", - "status": "READ" - }, - { - "name": "required_words", - "type": "ARRAY", - "status": "READ" - }, - { - "name": "forbidden_words", - "type": "ARRAY", - "status": "READ" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 15 - }, - { - "table": "magic_up_campaigns", - "module": "Magic Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ" - }, - { - "name": "client_name", - "type": "text", - "status": "READ" - }, - { - "name": "title", - "type": "text", - "status": "READ" - }, - { - "name": "objective", - "type": "text", - "status": "READ" - }, - { - "name": "channel", - "type": "text", - "status": "READ" - }, - { - "name": "audience", - "type": "text", - "status": "READ" - }, - { - "name": "tone", - "type": "text", - "status": "READ" - }, - { - "name": "cta", - "type": "text", - "status": "READ" - }, - { - "name": "occasion", - "type": "text", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 15 - }, - { - "table": "magic_up_comments", - "module": "Magic Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "generation_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "author_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "comment", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "is_public", - "type": "boolean", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 40, - "orphanCount": 3, - "totalCount": 7 - }, - { - "table": "magic_up_generations", - "module": "Magic Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_name", - "type": "text", - "status": "READ" - }, - { - "name": "scene_title", - "type": "text", - "status": "READ" - }, - { - "name": "scene_category", - "type": "text", - "status": "READ" - }, - { - "name": "client_name", - "type": "text", - "status": "READ" - }, - { - "name": "generated_image_url", - "type": "text", - "status": "READ" - }, - { - "name": "is_favorite", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "campaign_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "product_sku", - "type": "text", - "status": "READ" - }, - { - "name": "prompt_text", - "type": "text", - "status": "READ" - }, - { - "name": "model", - "type": "text", - "status": "READ" - }, - { - "name": "channel", - "type": "text", - "status": "READ" - }, - { - "name": "aspect_ratio", - "type": "text", - "status": "READ" - }, - { - "name": "quality_score", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "tags", - "type": "ARRAY", - "status": "READ" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "copy_pack", - "type": "jsonb", - "status": "READ" - }, - { - "name": "export_presets", - "type": "jsonb", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 22 - }, - { - "table": "magic_up_public_shares", - "module": "Magic Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "generation_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "campaign_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "share_token", - "type": "text", - "status": "READ" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "allow_download", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "allow_comments", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 56, - "orphanCount": 4, - "totalCount": 12 - }, - { - "table": "magic_up_reactions", - "module": "Magic Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "generation_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "reaction_type", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "ip_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 40, - "orphanCount": 3, - "totalCount": 7 - }, - { - "table": "mcp_access_violations", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "reason", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "source", - "type": "text", - "status": "READ" - }, - { - "name": "operation", - "type": "text", - "status": "READ" - }, - { - "name": "target_key_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "request_id", - "type": "text", - "status": "READ" - }, - { - "name": "details", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 78, - "orphanCount": 2, - "totalCount": 11 - }, - { - "table": "mcp_api_keys", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "key_hash", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "key_prefix", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "scopes", - "type": "ARRAY", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "last_used_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "revoked_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "rotated_from", - "type": "uuid", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 13 - }, - { - "table": "mcp_full_grantors", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "granted_by", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "reason", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "granted_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 25, - "orphanCount": 3, - "totalCount": 4 - }, - { - "table": "mcp_key_auto_revocations", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "key_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "revoked_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "source", - "type": "text", - "status": "READ" - }, - { - "name": "reason", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "mcp_keys", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "key_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "key_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "scopes", - "type": "ARRAY", - "status": "READ" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "last_used_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "is_revoked", - "type": "boolean", - "status": "ORPHAN" - } - ], - "coverage": 57, - "orphanCount": 3, - "totalCount": 9 - }, - { - "table": "mockup_drafts", - "module": "Mockups", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "draft_key", - "type": "text", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_name", - "type": "text", - "status": "READ" - }, - { - "name": "technique_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "technique_name", - "type": "text", - "status": "READ" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_name", - "type": "text", - "status": "READ" - }, - { - "name": "personalization_areas", - "type": "jsonb", - "status": "READ" - }, - { - "name": "logo_data", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 13 - }, - { - "table": "mockup_prompt_configs", - "module": "Mockups", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "config_key", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "label", - "type": "text", - "status": "READ" - }, - { - "name": "prompt_text", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "ai_model", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "technique_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ" - }, - { - "name": "version", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 10 - }, - { - "table": "mockup_prompt_history", - "module": "Mockups", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "config_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "config_key", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "old_prompt", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "new_prompt", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "ai_model", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "version", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "changed_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "change_notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "changed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 67, - "orphanCount": 3, - "totalCount": 10 - }, - { - "table": "mockup_templates", - "module": "Mockups", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "name", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "product_name", - "type": "text", - "status": "READ" - }, - { - "name": "technique_id", - "type": "text", - "status": "READ" - }, - { - "name": "technique_name", - "type": "text", - "status": "READ" - }, - { - "name": "personalization_areas", - "type": "jsonb", - "status": "READ" - }, - { - "name": "thumbnail_url", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "usage_count", - "type": "integer", - "status": "READ" - }, - { - "name": "is_favorite", - "type": "boolean", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 91, - "orphanCount": 1, - "totalCount": 14 - }, - { - "table": "optimization_queue", - "module": "Estoque", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "title", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "category", - "type": "text", - "status": "READ" - }, - { - "name": "priority", - "type": "integer", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "result", - "type": "jsonb", - "status": "READ" - }, - { - "name": "error", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "guardrail_status", - "type": "text", - "status": "READ" - }, - { - "name": "started_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "finished_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 14 - }, - { - "table": "optimization_queue_runs", - "module": "Estoque", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "queue_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "guardrail_status", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "executed_by", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 50, - "orphanCount": 3, - "totalCount": 8 - }, - { - "table": "order_item_personalizations", - "module": "Pedidos", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "order_item_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "technique_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "technique_name", - "type": "text", - "status": "READ" - }, - { - "name": "location_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "location_name", - "type": "text", - "status": "READ" - }, - { - "name": "image_url", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "personalization_text", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "price_adjustment", - "type": "numeric", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 38, - "orphanCount": 5, - "totalCount": 11 - }, - { - "table": "order_items", - "module": "Pedidos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "order_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "product_sku", - "type": "text", - "status": "READ" - }, - { - "name": "product_name", - "type": "text", - "status": "READ" - }, - { - "name": "product_image_url", - "type": "text", - "status": "READ" - }, - { - "name": "quantity", - "type": "integer", - "status": "READ" - }, - { - "name": "unit_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "organization_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "total_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "color_name", - "type": "text", - "status": "READ" - }, - { - "name": "color_hex", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "size_code", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "gender", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "kit_group_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "kit_name", - "type": "text", - "status": "ORPHAN" - } - ], - "coverage": 69, - "orphanCount": 5, - "totalCount": 18 - }, - { - "table": "orders", - "module": "Pedidos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "order_number", - "type": "text", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "fulfillment_status", - "type": "text", - "status": "READ" - }, - { - "name": "client_id", - "type": "text", - "status": "READ" - }, - { - "name": "client_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_email", - "type": "text", - "status": "READ" - }, - { - "name": "client_phone", - "type": "text", - "status": "READ" - }, - { - "name": "client_company", - "type": "text", - "status": "READ" - }, - { - "name": "quote_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "subtotal", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "discount_amount", - "type": "numeric", - "status": "READ" - }, - { - "name": "shipping_cost", - "type": "numeric", - "status": "READ" - }, - { - "name": "total", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "internal_notes", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "tracking_number", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "shipping_type", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "payment_terms", - "type": "text", - "status": "READ" - }, - { - "name": "delivery_time", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "organization_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "version", - "type": "integer", - "status": "READ" - } - ], - "coverage": 86, - "orphanCount": 3, - "totalCount": 25 - }, - { - "table": "organization_members", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "organization_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "role", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "invited_by", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "joined_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 80, - "orphanCount": 1, - "totalCount": 8 - }, - { - "table": "organizations", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "name", - "type": "text", - "status": "READ" - }, - { - "name": "slug", - "type": "text", - "status": "READ" - }, - { - "name": "logo_url", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ" - }, - { - "name": "settings", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "outbound_webhooks", - "module": "Webhooks & Conexões", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "secret_ref", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "events", - "type": "ARRAY", - "status": "READ+WRITE" - }, - { - "name": "active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "retry_policy", - "type": "jsonb", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "created_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "last_triggered_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "total_success", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "total_failure", - "type": "integer", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "consecutive_failures", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "auto_disabled_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "auto_disabled_reason", - "type": "text", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 17 - }, - { - "table": "ownership_audit_reports", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "generated_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "total_tables_scanned", - "type": "integer", - "status": "READ" - }, - { - "name": "total_issues_found", - "type": "integer", - "status": "READ" - }, - { - "name": "null_owner_count", - "type": "integer", - "status": "READ" - }, - { - "name": "missing_user_count", - "type": "integer", - "status": "READ" - }, - { - "name": "details", - "type": "jsonb", - "status": "READ" - }, - { - "name": "triggered_by", - "type": "text", - "status": "READ" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "rls_coverage", - "type": "jsonb", - "status": "READ" - }, - { - "name": "rls_gaps_count", - "type": "integer", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "ownership_repair_logs", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "report_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "table_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "owner_column", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "issue_type", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "action", - "type": "text", - "status": "READ" - }, - { - "name": "rows_affected", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "dry_run", - "type": "boolean", - "status": "READ" - }, - { - "name": "triggered_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "triggered_by_label", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "error_message", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 55, - "orphanCount": 5, - "totalCount": 13 - }, - { - "table": "permissions", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "code", - "type": "text", - "status": "READ" - }, - { - "name": "name", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "category", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "price_history", - "module": "Preços & Descontos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "variant_id", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "price", - "type": "numeric", - "status": "READ" - }, - { - "name": "recorded_at", - "type": "timestamp with time zone", - "status": "READ" - } - ], - "coverage": 75, - "orphanCount": 1, - "totalCount": 5 - }, - { - "table": "product_component_locations", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "component_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "location_code", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "location_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "max_width_cm", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "max_height_cm", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "product_components", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "component_code", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "component_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_personalizable", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "product_group_members", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "product_group_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "use_group_rules", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "product_groups", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "group_code", - "type": "text", - "status": "READ" - }, - { - "name": "group_name", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "product_price_freshness_overrides", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "threshold_days", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "updated_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "product_sync_logs", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "source", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "records_processed", - "type": "integer", - "status": "READ" - }, - { - "name": "records_inserted", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "records_updated", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "records_failed", - "type": "integer", - "status": "READ" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "payload", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "error_message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "triggered_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 80, - "orphanCount": 2, - "totalCount": 12 - }, - { - "table": "product_views", - "module": "Catálogo / Produtos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_sku", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "view_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "profiles", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "email", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "full_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "role", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "avatar_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "phone", - "type": "text", - "status": "READ" - }, - { - "name": "department", - "type": "text", - "status": "READ" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "last_login_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "preferences", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 13 - }, - { - "table": "public_token_failures", - "module": "Segurança", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "resource_type", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "resource_id", - "type": "text", - "status": "READ" - }, - { - "name": "attempted_token", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "ip_address", - "type": "text", - "status": "READ" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "reason", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 50, - "orphanCount": 3, - "totalCount": 8 - }, - { - "table": "query_telemetry", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "operation", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "table_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "rpc_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "record_count", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "query_limit", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "query_offset", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "count_mode", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "severity", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "error_message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "error_kind", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "retry_count", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "cache_hit", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "is_cold_start", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "is_503", - "type": "boolean", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 18 - }, - { - "table": "quote_approval_tokens", - "module": "Orçamentos", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_id", - "type": "text", - "status": "READ" - }, - { - "name": "token", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "client_name", - "type": "text", - "status": "READ" - }, - { - "name": "client_email", - "type": "text", - "status": "READ" - }, - { - "name": "status", - "type": "text", - "status": "READ" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "viewed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "responded_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "response", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "response_notes", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "signer_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "signer_document", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "signer_ip", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "signer_user_agent", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "signature_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "signed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 35, - "orphanCount": 11, - "totalCount": 20 - }, - { - "table": "quote_comments", - "module": "Orçamentos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "parent_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "content", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_edited", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "quote_drafts", - "module": "Orçamentos", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "data", - "type": "jsonb", - "status": "ORPHAN" - }, - { - "name": "last_saved_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 33, - "orphanCount": 2, - "totalCount": 4 - }, - { - "table": "quote_history", - "module": "Orçamentos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "action", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "field_changed", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "old_value", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "new_value", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 10 - }, - { - "table": "quote_item_personalizations", - "module": "Orçamentos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_item_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "technique_id", - "type": "text", - "status": "READ" - }, - { - "name": "technique_name", - "type": "text", - "status": "READ" - }, - { - "name": "colors_count", - "type": "integer", - "status": "READ" - }, - { - "name": "positions_count", - "type": "integer", - "status": "READ" - }, - { - "name": "area_cm2", - "type": "numeric", - "status": "READ" - }, - { - "name": "width_cm", - "type": "numeric", - "status": "READ" - }, - { - "name": "height_cm", - "type": "numeric", - "status": "READ" - }, - { - "name": "personalized_quantity", - "type": "integer", - "status": "READ" - }, - { - "name": "setup_cost", - "type": "numeric", - "status": "READ" - }, - { - "name": "unit_cost", - "type": "numeric", - "status": "READ" - }, - { - "name": "total_cost", - "type": "numeric", - "status": "READ" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 16 - }, - { - "table": "quote_items", - "module": "Orçamentos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "product_name", - "type": "text", - "status": "READ" - }, - { - "name": "product_sku", - "type": "text", - "status": "READ" - }, - { - "name": "product_image_url", - "type": "text", - "status": "READ" - }, - { - "name": "quantity", - "type": "integer", - "status": "READ" - }, - { - "name": "unit_price", - "type": "numeric", - "status": "READ" - }, - { - "name": "subtotal", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "color_name", - "type": "text", - "status": "READ" - }, - { - "name": "color_hex", - "type": "text", - "status": "READ" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ" - }, - { - "name": "display_order", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "kit_group_id", - "type": "text", - "status": "READ" - }, - { - "name": "kit_name", - "type": "text", - "status": "READ" - }, - { - "name": "size_code", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "gender", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "price_confirmed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 78, - "orphanCount": 4, - "totalCount": 21 - }, - { - "table": "quote_templates", - "module": "Orçamentos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "name", - "type": "text", - "status": "READ" - }, - { - "name": "description", - "type": "text", - "status": "READ" - }, - { - "name": "is_default", - "type": "boolean", - "status": "READ" - }, - { - "name": "template_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "items_data", - "type": "jsonb", - "status": "READ" - }, - { - "name": "discount_percent", - "type": "numeric", - "status": "READ" - }, - { - "name": "discount_amount", - "type": "numeric", - "status": "READ" - }, - { - "name": "notes", - "type": "text", - "status": "READ" - }, - { - "name": "internal_notes", - "type": "text", - "status": "READ" - }, - { - "name": "payment_terms", - "type": "text", - "status": "READ" - }, - { - "name": "delivery_time", - "type": "text", - "status": "READ" - }, - { - "name": "validity_days", - "type": "integer", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 16 - }, - { - "table": "quotes", - "module": "Orçamentos", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "quote_number", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_email", - "type": "text", - "status": "READ" - }, - { - "name": "client_phone", - "type": "text", - "status": "READ" - }, - { - "name": "client_company", - "type": "text", - "status": "READ" - }, - { - "name": "client_cnpj", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "subtotal", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "discount_percent", - "type": "numeric", - "status": "READ" - }, - { - "name": "discount_amount", - "type": "numeric", - "status": "READ" - }, - { - "name": "total", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "payment_terms", - "type": "text", - "status": "READ" - }, - { - "name": "delivery_time", - "type": "text", - "status": "READ" - }, - { - "name": "shipping_type", - "type": "text", - "status": "READ" - }, - { - "name": "shipping_cost", - "type": "numeric", - "status": "READ" - }, - { - "name": "internal_notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "valid_until", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "bitrix_deal_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "bitrix_quote_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "synced_to_bitrix", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "synced_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "client_response", - "type": "text", - "status": "READ" - }, - { - "name": "client_response_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "client_response_notes", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "sent_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "version", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "parent_quote_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "is_latest_version", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "organization_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "negotiation_markup_percent", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "real_subtotal", - "type": "numeric", - "status": "READ" - }, - { - "name": "real_discount_percent", - "type": "numeric", - "status": "READ+WRITE" - } - ], - "coverage": 94, - "orphanCount": 2, - "totalCount": 38 - }, - { - "table": "recently_viewed_products", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "product_id", - "type": "text", - "status": "READ" - }, - { - "name": "viewed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 67, - "orphanCount": 1, - "totalCount": 4 - }, - { - "table": "request_rate_limits", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "identifier", - "type": "text", - "status": "READ" - }, - { - "name": "endpoint", - "type": "text", - "status": "READ" - }, - { - "name": "request_count", - "type": "integer", - "status": "READ" - }, - { - "name": "window_start", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "blocked_until", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "rls_denial_log", - "module": "Auditoria", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "user_email", - "type": "text", - "status": "READ" - }, - { - "name": "user_role", - "type": "text", - "status": "READ" - }, - { - "name": "table_name", - "type": "text", - "status": "READ" - }, - { - "name": "operation", - "type": "text", - "status": "READ" - }, - { - "name": "endpoint", - "type": "text", - "status": "READ" - }, - { - "name": "query_summary", - "type": "text", - "status": "READ" - }, - { - "name": "target_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "target_seller_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "policy_hint", - "type": "text", - "status": "READ" - }, - { - "name": "error_code", - "type": "text", - "status": "READ" - }, - { - "name": "error_message", - "type": "text", - "status": "READ" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "ip_address", - "type": "inet", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 16 - }, - { - "table": "role_migration_batches", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "label", - "type": "text", - "status": "READ" - }, - { - "name": "reason", - "type": "text", - "status": "READ" - }, - { - "name": "initiated_by", - "type": "uuid", - "status": "READ" - }, - { - "name": "dry_run", - "type": "boolean", - "status": "READ" - }, - { - "name": "status", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "total_items", - "type": "integer", - "status": "READ" - }, - { - "name": "success_count", - "type": "integer", - "status": "READ" - }, - { - "name": "failed_count", - "type": "integer", - "status": "READ" - }, - { - "name": "skipped_count", - "type": "integer", - "status": "READ" - }, - { - "name": "started_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "finished_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 14 - }, - { - "table": "role_migration_items", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "batch_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "user_email", - "type": "text", - "status": "READ" - }, - { - "name": "from_role", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "to_role", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "operation", - "type": "text", - "status": "READ" - }, - { - "name": "status", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "error_message", - "type": "text", - "status": "READ" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "processed_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 12 - }, - { - "table": "role_permissions", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "role", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "permission_code", - "type": "text", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 4 - }, - { - "table": "saved_filters", - "module": "Simulador & Filtros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "filters", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "context", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_default", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "icon", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "saved_trends_views", - "module": "Simulador & Filtros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "filters", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 6 - }, - { - "table": "scheduled_reports", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "report_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "frequency", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "email_to", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "report_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "filters", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "is_active", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "last_sent_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "next_run_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 12 - }, - { - "table": "search_analytics", - "module": "SEO & Busca", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "search_term", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "results_count", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "search_context", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 75, - "orphanCount": 1, - "totalCount": 6 - }, - { - "table": "secret_rotation_log", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "secret_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "rotated_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "rotated_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "previous_suffix", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "new_suffix", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "action_type", - "type": "text", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "seller_cart_items", - "module": "Carrinhos de Vendedor", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "cart_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_sku", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_image_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_price", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "quantity", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "color_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "color_hex", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "sort_order", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 14 - }, - { - "table": "seller_carts", - "module": "Carrinhos de Vendedor", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "seller_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "company_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "company_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "company_location", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "company_logo_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "status", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 10 - }, - { - "table": "seller_discount_limits", - "module": "Carrinhos de Vendedor", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "max_discount_percent", - "type": "numeric", - "status": "READ+WRITE" - }, - { - "name": "set_by", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "notes", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 7 - }, - { - "table": "simulator_wizard_drafts", - "module": "Simulador & Filtros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "title", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_data", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "quantity", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "personalizations", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "wizard_step", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "step_up_audit_log", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "action", - "type": "USER-DEFINED", - "status": "READ+WRITE" - }, - { - "name": "target_ref", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "event_type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "challenge_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "token_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "ip_address", - "type": "inet", - "status": "READ+WRITE" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 89, - "orphanCount": 1, - "totalCount": 11 - }, - { - "table": "step_up_challenges", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "action", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "target_ref", - "type": "text", - "status": "READ" - }, - { - "name": "otp_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "attempts", - "type": "smallint", - "status": "READ" - }, - { - "name": "max_attempts", - "type": "smallint", - "status": "ORPHAN" - }, - { - "name": "password_verified", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "otp_verified", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "consumed", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "ip_address", - "type": "inet", - "status": "READ" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - } - ], - "coverage": 58, - "orphanCount": 5, - "totalCount": 14 - }, - { - "table": "step_up_tokens", - "module": "MCP & Step-Up", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "action", - "type": "USER-DEFINED", - "status": "READ" - }, - { - "name": "target_ref", - "type": "text", - "status": "READ" - }, - { - "name": "token_hash", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "challenge_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "consumed", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "expires_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "consumed_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 50, - "orphanCount": 4, - "totalCount": 10 - }, - { - "table": "system_settings", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "key", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "value", - "type": "jsonb", - "status": "READ" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_by", - "type": "uuid", - "status": "READ" - } - ], - "coverage": 67, - "orphanCount": 1, - "totalCount": 4 - }, - { - "table": "user_comparisons", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "client_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "client_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "text", - "status": "READ" - }, - { - "name": "items", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "share_token", - "type": "text", - "status": "READ" - }, - { - "name": "is_public", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "share_expires_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "view_count", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 89, - "orphanCount": 1, - "totalCount": 12 - }, - { - "table": "user_known_devices", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "fingerprint", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "device_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "last_seen_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 75, - "orphanCount": 1, - "totalCount": 6 - }, - { - "table": "user_onboarding", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "has_completed_tour", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "current_step", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "completed_steps", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "started_at", - "type": "timestamp with time zone", - "status": "READ" - }, - { - "name": "completed_at", - "type": "timestamp with time zone", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "user_preferences", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "comparison_weights", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "comparison_column_order", - "type": "jsonb", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "filter_states", - "type": "jsonb", - "status": "ORPHAN" - } - ], - "coverage": 50, - "orphanCount": 2, - "totalCount": 7 - }, - { - "table": "user_roles", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "role", - "type": "USER-DEFINED", - "status": "READ+WRITE" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 3 - }, - { - "table": "user_search_history", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "query_text", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "history_type", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "result_count", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "is_pinned", - "type": "boolean", - "status": "ORPHAN" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 33, - "orphanCount": 4, - "totalCount": 9 - }, - { - "table": "user_token_revocations", - "module": "Usuários & RBAC", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "user_id", - "type": "uuid", - "status": "READ" - }, - { - "name": "revoked_at", - "type": "timestamp with time zone", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 2 - }, - { - "table": "v_full_scope_grants", - "module": "Outros", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "audit_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "granted_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "granted_to_user_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "granted_to_name", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "granted_to_email", - "type": "character varying", - "status": "ORPHAN" - }, - { - "name": "step_up_action", - "type": "USER-DEFINED", - "status": "ORPHAN" - }, - { - "name": "operation", - "type": "text", - "status": "READ" - }, - { - "name": "key_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "key_prefix", - "type": "text", - "status": "READ" - }, - { - "name": "key_expires_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - }, - { - "name": "justification", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "challenge_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "token_id", - "type": "uuid", - "status": "ORPHAN" - }, - { - "name": "ip_address", - "type": "inet", - "status": "READ" - }, - { - "name": "user_agent", - "type": "text", - "status": "READ" - }, - { - "name": "request_id", - "type": "text", - "status": "READ" - }, - { - "name": "verifications_applied", - "type": "jsonb", - "status": "ORPHAN" - }, - { - "name": "extra", - "type": "jsonb", - "status": "ORPHAN" - } - ], - "coverage": 28, - "orphanCount": 13, - "totalCount": 18 - }, - { - "table": "video_variant_links", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "video_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_name", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "variant_color_hex", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "supplier_code", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "product_id", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 8 - }, - { - "table": "voice_command_logs", - "module": "Outros", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "transcript", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "action", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "response", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "data", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "success", - "type": "boolean", - "status": "READ" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 9 - }, - { - "table": "webhook_deliveries", - "module": "Webhooks & Conexões", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "webhook_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "event", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "payload", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "payload_hash", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "status_code", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "response_body_truncated", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "attempt", - "type": "integer", - "status": "READ+WRITE" - }, - { - "name": "success", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "error_message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "delivered_at", - "type": "timestamp with time zone", - "status": "READ" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 11 - }, - { - "table": "webhook_delivery_metrics", - "module": "Webhooks & Conexões", - "rows": 0, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "request_id", - "type": "text", - "status": "READ" - }, - { - "name": "event_type", - "type": "text", - "status": "READ" - }, - { - "name": "source", - "type": "text", - "status": "READ" - }, - { - "name": "direction", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "endpoint", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "http_status", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "duration_ms", - "type": "integer", - "status": "READ" - }, - { - "name": "attempt", - "type": "integer", - "status": "READ" - }, - { - "name": "success", - "type": "boolean", - "status": "READ" - }, - { - "name": "error_class", - "type": "text", - "status": "ORPHAN" - }, - { - "name": "error_message", - "type": "text", - "status": "READ" - }, - { - "name": "payload_bytes", - "type": "integer", - "status": "ORPHAN" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ" - }, - { - "name": "occurred_at", - "type": "timestamp with time zone", - "status": "ORPHAN" - } - ], - "coverage": 57, - "orphanCount": 6, - "totalCount": 15 - }, - { - "table": "webhook_delivery_metrics_y2026m05", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "webhook_delivery_metrics_y2026m06", - "excluded": "name-pattern", - "rows": 0 - }, - { - "table": "workspace_notifications", - "module": "Infra & Observabilidade", - "rows": 0, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "uuid", - "status": "SYSTEM" - }, - { - "name": "user_id", - "type": "uuid", - "status": "READ+WRITE" - }, - { - "name": "title", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "message", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "type", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "category", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "is_read", - "type": "boolean", - "status": "READ+WRITE" - }, - { - "name": "action_url", - "type": "text", - "status": "READ+WRITE" - }, - { - "name": "metadata", - "type": "jsonb", - "status": "READ+WRITE" - }, - { - "name": "created_at", - "type": "timestamp with time zone", - "status": "SYSTEM" - } - ], - "coverage": 100, - "orphanCount": 0, - "totalCount": 10 - } - ] - }, - { - "label": "BD Externo (produtos SSOT)", - "tables": [ - { - "table": "products", - "module": "Catálogo / Produtos", - "rows": -1, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "sku", - "type": "unknown", - "status": "READ+WRITE" - }, - { - "name": "name", - "type": "unknown", - "status": "READ+WRITE" - }, - { - "name": "description", - "type": "unknown", - "status": "READ" - }, - { - "name": "short_description", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "category_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "brand", - "type": "unknown", - "status": "READ" - }, - { - "name": "sale_price", - "type": "unknown", - "status": "READ" - }, - { - "name": "base_price", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "cost_price", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "stock_quantity", - "type": "unknown", - "status": "READ" - }, - { - "name": "is_bestseller", - "type": "unknown", - "status": "READ" - }, - { - "name": "is_new", - "type": "unknown", - "status": "READ" - }, - { - "name": "is_kit", - "type": "unknown", - "status": "READ" - }, - { - "name": "supplier_code", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "supplier_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "image_url", - "type": "unknown", - "status": "READ" - }, - { - "name": "videos", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "category_id", - "type": "unknown", - "status": "READ" - }, - { - "name": "price", - "type": "unknown", - "status": "READ" - }, - { - "name": "og_image_url", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "images", - "type": "unknown", - "status": "READ" - }, - { - "name": "stock", - "type": "unknown", - "status": "READ" - }, - { - "name": "created_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "colors", - "type": "unknown", - "status": "READ" - }, - { - "name": "materials", - "type": "unknown", - "status": "READ" - }, - { - "name": "supplier_reference", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_active", - "type": "unknown", - "status": "READ" - }, - { - "name": "minQuantity", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "dimensions", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "height_cm", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "width_cm", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "length_cm", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "diameter_cm", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "weight_g", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "capacity_ml", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "stock_status", - "type": "unknown", - "status": "READ" - }, - { - "name": "subcategory", - "type": "unknown", - "status": "READ" - }, - { - "name": "supplier_id", - "type": "unknown", - "status": "READ" - }, - { - "name": "variations", - "type": "unknown", - "status": "READ" - }, - { - "name": "tags", - "type": "unknown", - "status": "READ" - }, - { - "name": "featured", - "type": "unknown", - "status": "READ" - }, - { - "name": "new_arrival", - "type": "unknown", - "status": "READ" - }, - { - "name": "on_sale", - "type": "unknown", - "status": "READ" - }, - { - "name": "kit_items", - "type": "unknown", - "status": "READ" - }, - { - "name": "min_quantity", - "type": "unknown", - "status": "READ" - }, - { - "name": "external_id", - "type": "unknown", - "status": "READ" - }, - { - "name": "metadata", - "type": "unknown", - "status": "READ+WRITE" - } - ], - "coverage": 67, - "orphanCount": 15, - "totalCount": 49 - }, - { - "table": "tecnicas_gravacao", - "module": "Outros", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "name", - "type": "unknown", - "status": "READ" - }, - { - "name": "technique_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "code", - "type": "unknown", - "status": "READ" - }, - { - "name": "technique_code", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "setup_cost", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "setup_price", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "unit_cost", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "handling_price", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "max_colors", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "min_area_cm2", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "max_area_cm2", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "sla_days", - "type": "unknown", - "status": "ORPHAN" - } - ], - "coverage": 25, - "orphanCount": 9, - "totalCount": 13 - }, - { - "table": "print_areas", - "module": "Outros", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "component_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "location_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "width_cm", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "height_cm", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "unit", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_primary", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "allowed_technique_ids", - "type": "unknown", - "status": "ORPHAN" - } - ], - "coverage": 29, - "orphanCount": 5, - "totalCount": 8 - }, - { - "table": "product_colors_view", - "module": "Catálogo / Produtos", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "name", - "type": "unknown", - "status": "READ" - }, - { - "name": "hex", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "group", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "groupSlug", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "variationSlug", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "code", - "type": "unknown", - "status": "READ" - }, - { - "name": "image", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "images", - "type": "unknown", - "status": "READ" - } - ], - "coverage": 38, - "orphanCount": 5, - "totalCount": 8 - }, - { - "table": "stock_movements", - "module": "Estoque", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "productId", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "variantId", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "colorName", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "type", - "type": "unknown", - "status": "READ" - }, - { - "name": "quantity", - "type": "unknown", - "status": "READ" - }, - { - "name": "previousStock", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "newStock", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "reason", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "reference", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "referenceType", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "unitCost", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "totalCost", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "createdAt", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "createdBy", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "createdByName", - "type": "unknown", - "status": "ORPHAN" - } - ], - "coverage": 13, - "orphanCount": 13, - "totalCount": 16 - } - ] - }, - { - "label": "BD CRM (Bitrix mirror)", - "tables": [ - { - "table": "companies", - "module": "CRM", - "rows": -1, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "razao_social", - "type": "unknown", - "status": "READ" - }, - { - "name": "nome_fantasia", - "type": "unknown", - "status": "READ" - }, - { - "name": "title", - "type": "unknown", - "status": "READ" - }, - { - "name": "cnpj", - "type": "unknown", - "status": "READ" - }, - { - "name": "ramo_atividade", - "type": "unknown", - "status": "READ" - }, - { - "name": "status", - "type": "unknown", - "status": "READ" - }, - { - "name": "source", - "type": "unknown", - "status": "READ" - }, - { - "name": "is_customer", - "type": "unknown", - "status": "READ" - }, - { - "name": "is_supplier", - "type": "unknown", - "status": "READ" - }, - { - "name": "is_carrier", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_matriz", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "logradouro", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "numero", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "complemento", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "bairro", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "cidade", - "type": "unknown", - "status": "READ" - }, - { - "name": "estado", - "type": "unknown", - "status": "READ" - }, - { - "name": "cep", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "pais", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "endereco", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "endereco_faturamento", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "inscricao_estadual", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "inscricao_municipal", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "cnae_principal", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "cnae_descricao", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "website", - "type": "unknown", - "status": "READ" - }, - { - "name": "instagram", - "type": "unknown", - "status": "READ" - }, - { - "name": "facebook", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "linkedin", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "logo_url", - "type": "unknown", - "status": "READ" - }, - { - "name": "grupo_economico", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "grupo_economico_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "matriz_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "central_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "singular_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "tipo_cooperativa", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "employee_count", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "annual_revenue", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "financial_health", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "bitrix_company_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "bitrix_created_at", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "bitrix_updated_at", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "_deprecated_email", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "_deprecated_phone", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "_deprecated_phone_secondary", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "tags_array", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "challenges", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "competitors", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "search_vector", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "deleted_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "deleted_by", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "user_id", - "type": "unknown", - "status": "READ" - }, - { - "name": "assigned_by_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "created_by_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "merge_notes", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "unknown", - "status": "SYSTEM" - } - ], - "coverage": 28, - "orphanCount": 39, - "totalCount": 58 - }, - { - "table": "contacts", - "module": "CRM", - "rows": -1, - "tableSeen": true, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "company_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "first_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "last_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "full_name", - "type": "unknown", - "status": "READ" - }, - { - "name": "nome_tratamento", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "apelido", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "cargo", - "type": "unknown", - "status": "READ" - }, - { - "name": "departamento", - "type": "unknown", - "status": "READ" - }, - { - "name": "role", - "type": "unknown", - "status": "READ" - }, - { - "name": "cpf", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "sexo", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "birthday", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "data_nascimento", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "linkedin", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "instagram", - "type": "unknown", - "status": "READ" - }, - { - "name": "notes", - "type": "unknown", - "status": "READ" - }, - { - "name": "source", - "type": "unknown", - "status": "READ" - }, - { - "name": "sentiment", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "relationship_score", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "relationship_stage", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "behavior", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "hobbies", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "interests_array", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "tags_array", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "bitrix_contact_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "deleted_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "deleted_by", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "user_id", - "type": "unknown", - "status": "READ" - }, - { - "name": "assigned_by_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "_deprecated_email", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "_deprecated_phone", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "_deprecated_whatsapp", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "emails", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "phones", - "type": "unknown", - "status": "ORPHAN" - } - ], - "coverage": 30, - "orphanCount": 23, - "totalCount": 37 - }, - { - "table": "contact_emails", - "module": "Outros", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "contact_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "email", - "type": "unknown", - "status": "READ" - }, - { - "name": "email_normalizado", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "email_type", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_primary", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_verified", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "unknown", - "status": "SYSTEM" - } - ], - "coverage": 17, - "orphanCount": 5, - "totalCount": 9 - }, - { - "table": "contact_phones", - "module": "Outros", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "contact_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "numero", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "numero_normalizado", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "numero_e164", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "phone_type", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_primary", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_whatsapp", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_verified", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "observacao", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "unknown", - "status": "SYSTEM" - } - ], - "coverage": 0, - "orphanCount": 9, - "totalCount": 12 - }, - { - "table": "company_addresses", - "module": "CRM", - "rows": -1, - "tableSeen": false, - "columns": [ - { - "name": "id", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "company_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "tipo", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "is_primary", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "logradouro", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "numero", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "complemento", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "bairro", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "cidade", - "type": "unknown", - "status": "READ" - }, - { - "name": "estado", - "type": "unknown", - "status": "READ" - }, - { - "name": "cep", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "pais", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "google_maps_url", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "google_place_id", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "latitude", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "longitude", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "horario_funcionamento", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "instrucoes_entrega", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "ponto_referencia", - "type": "unknown", - "status": "ORPHAN" - }, - { - "name": "created_at", - "type": "unknown", - "status": "SYSTEM" - }, - { - "name": "updated_at", - "type": "unknown", - "status": "SYSTEM" - } - ], - "coverage": 11, - "orphanCount": 16, - "totalCount": 21 - } - ] - } - ] -} \ No newline at end of file diff --git a/audit/internal-schema.tsv b/audit/internal-schema.tsv deleted file mode 100644 index f5c3f84fb..000000000 --- a/audit/internal-schema.tsv +++ /dev/null @@ -1,3647 +0,0 @@ -_asia_api_staging referencia text 0 -_asia_api_staging nome text 0 -_asia_api_staging raw_json jsonb 0 -_asia_api_staging propriedades jsonb 0 -_asia_api_staging altura integer 0 -_asia_api_staging largura integer 0 -_asia_api_staging comprimento integer 0 -_asia_api_staging peso numeric 0 -_asia_api_staging origem_faturamento character varying 0 -_asia_api_staging promocao integer 0 -_asia_api_staging categorias jsonb 0 -_asia_api_staging tags jsonb 0 -_asia_api_staging galeria jsonb 0 -_asia_api_staging video text 0 -_asia_api_staging total_variacoes integer 0 -_asia_api_staging created_at timestamp with time zone 0 -_unif_pending_log id integer 0 -_unif_pending_log ticket text 0 -_unif_pending_log prioridade text 0 -_unif_pending_log acao text 0 -_unif_pending_log status text 0 -_unif_pending_log decisao_em timestamp without time zone 0 -_unif_pending_log revisar_em date 0 -_unif_pending_log contexto jsonb 0 -_unif_pending_log observacao text 0 -_unif_settings_arquivo id bigint 0 -_unif_settings_arquivo setting_key text 0 -_unif_settings_arquivo setting_value jsonb 0 -_unif_settings_arquivo description text 0 -_unif_settings_arquivo arquivado_em timestamp with time zone 0 -_unif_settings_arquivo motivo text 0 -access_security_settings id uuid 0 -access_security_settings ip_whitelist_enabled boolean 0 -access_security_settings city_whitelist_enabled boolean 0 -access_security_settings block_unknown_locations boolean 0 -access_security_settings max_failed_attempts integer 0 -access_security_settings lockout_duration_minutes integer 0 -access_security_settings strict_access_mode boolean 0 -access_security_settings created_at timestamp with time zone 0 -access_security_settings updated_at timestamp with time zone 0 -admin_audit_log id uuid 0 -admin_audit_log user_id uuid 0 -admin_audit_log action text 0 -admin_audit_log resource_type text 0 -admin_audit_log resource_id text 0 -admin_audit_log details jsonb 0 -admin_audit_log ip_address text 0 -admin_audit_log user_agent text 0 -admin_audit_log created_at timestamp with time zone 0 -admin_audit_log request_id text 0 -admin_audit_log started_at timestamp with time zone 0 -admin_audit_log finished_at timestamp with time zone 0 -admin_audit_log duration_ms integer 0 -admin_audit_log status text 0 -admin_audit_log payload_summary jsonb 0 -admin_audit_log source text 0 -admin_settings id uuid 0 -admin_settings key text 0 -admin_settings value jsonb 0 -admin_settings updated_by uuid 0 -admin_settings created_at timestamp with time zone 0 -admin_settings updated_at timestamp with time zone 0 -ai_description_queue id uuid 0 -ai_description_queue product_id uuid 0 -ai_description_queue status text 0 -ai_description_queue priority integer 0 -ai_description_queue attempts integer 0 -ai_description_queue max_attempts integer 0 -ai_description_queue error_message text 0 -ai_description_queue ai_model text 0 -ai_description_queue queued_at timestamp with time zone 0 -ai_description_queue started_at timestamp with time zone 0 -ai_description_queue completed_at timestamp with time zone 0 -ai_description_queue organization_id uuid 0 -ai_function_routing id uuid 0 -ai_function_routing function_name text 0 -ai_function_routing primary_model_id uuid 0 -ai_function_routing fallback_model_ids ARRAY 0 -ai_function_routing required_capabilities jsonb 0 -ai_function_routing request_overrides jsonb 0 -ai_function_routing is_active boolean 0 -ai_function_routing notes text 0 -ai_function_routing created_at timestamp with time zone 0 -ai_function_routing updated_at timestamp with time zone 0 -ai_function_routing updated_by uuid 0 -ai_insights_cache id uuid 0 -ai_insights_cache user_id uuid 0 -ai_insights_cache function_name text 0 -ai_insights_cache cache_key text 0 -ai_insights_cache payload jsonb 0 -ai_insights_cache model text 0 -ai_insights_cache tokens_input integer 0 -ai_insights_cache tokens_output integer 0 -ai_insights_cache duration_ms integer 0 -ai_insights_cache created_at timestamp with time zone 0 -ai_insights_cache expires_at timestamp with time zone 0 -ai_models id uuid 0 -ai_models provider_id uuid 0 -ai_models model_id text 0 -ai_models display_name text 0 -ai_models capabilities jsonb 0 -ai_models cost_input_per_1m numeric 0 -ai_models cost_output_per_1m numeric 0 -ai_models cost_per_image numeric 0 -ai_models max_input_tokens integer 0 -ai_models max_output_tokens integer 0 -ai_models is_active boolean 0 -ai_models metadata jsonb 0 -ai_models created_at timestamp with time zone 0 -ai_models updated_at timestamp with time zone 0 -ai_provider_quotas id uuid 0 -ai_provider_quotas provider_id uuid 0 -ai_provider_quotas monthly_limit_usd numeric 0 -ai_provider_quotas monthly_limit_tokens bigint 0 -ai_provider_quotas alert_threshold_pct integer 0 -ai_provider_quotas hard_stop boolean 0 -ai_provider_quotas current_period_start date 0 -ai_provider_quotas current_period_usage_usd numeric 0 -ai_provider_quotas current_period_usage_tokens bigint 0 -ai_provider_quotas is_active boolean 0 -ai_provider_quotas notes text 0 -ai_provider_quotas created_at timestamp with time zone 0 -ai_provider_quotas updated_at timestamp with time zone 0 -ai_providers id uuid 0 -ai_providers slug text 0 -ai_providers display_name text 0 -ai_providers api_base_url text 0 -ai_providers api_format text 0 -ai_providers auth_header text 0 -ai_providers auth_format text 0 -ai_providers secret_name text 0 -ai_providers is_active boolean 0 -ai_providers priority integer 0 -ai_providers timeout_ms integer 0 -ai_providers max_retries integer 0 -ai_providers metadata jsonb 0 -ai_providers last_test_at timestamp with time zone 0 -ai_providers last_test_ok boolean 0 -ai_providers last_test_message text 0 -ai_providers last_latency_ms integer 0 -ai_providers created_at timestamp with time zone 0 -ai_providers updated_at timestamp with time zone 0 -ai_providers created_by uuid 0 -ai_providers updated_by uuid 0 -ai_routing_decisions id uuid 0 -ai_routing_decisions function_name text 0 -ai_routing_decisions user_id uuid 0 -ai_routing_decisions attempted_models ARRAY 0 -ai_routing_decisions attempted_providers ARRAY 0 -ai_routing_decisions attempted_outcomes jsonb 0 -ai_routing_decisions final_model_id uuid 0 -ai_routing_decisions final_provider_id uuid 0 -ai_routing_decisions total_attempts integer 0 -ai_routing_decisions total_duration_ms integer 0 -ai_routing_decisions outcome text 0 -ai_routing_decisions usage_log_id uuid 0 -ai_routing_decisions request_id text 0 -ai_routing_decisions created_at timestamp with time zone 0 -ai_usage_events id uuid 0 -ai_usage_events user_id uuid 0 -ai_usage_events function_name text 0 -ai_usage_events event_type text 0 -ai_usage_events metadata jsonb 0 -ai_usage_events created_at timestamp with time zone 0 -ai_usage_logs id uuid 0 -ai_usage_logs user_id uuid 0 -ai_usage_logs function_name text 0 -ai_usage_logs model text 0 -ai_usage_logs input_tokens integer 0 -ai_usage_logs output_tokens integer 0 -ai_usage_logs total_tokens integer 0 -ai_usage_logs estimated_cost_usd numeric 0 -ai_usage_logs duration_ms integer 0 -ai_usage_logs status text 0 -ai_usage_logs error_message text 0 -ai_usage_logs metadata jsonb 0 -ai_usage_logs created_at timestamp with time zone 0 -ai_usage_quotas id uuid 0 -ai_usage_quotas role USER-DEFINED 0 -ai_usage_quotas monthly_limit integer 0 -ai_usage_quotas is_unlimited boolean 0 -ai_usage_quotas created_at timestamp with time zone 0 -ai_usage_quotas updated_at timestamp with time zone 0 -analytics_events id uuid 0 -analytics_events user_id uuid 0 -analytics_events event_type text 0 -analytics_events event_name text 0 -analytics_events properties jsonb 0 -analytics_events session_id text 0 -analytics_events ip_address inet 0 -analytics_events user_agent text 0 -analytics_events created_at timestamp with time zone 0 -api_usage id uuid 0 -api_usage provider text 0 -api_usage period_month text 0 -api_usage credits_used integer 0 -api_usage credits_limit integer 0 -api_usage cost_usd numeric 0 -api_usage updated_at timestamp with time zone 0 -app_vitals id uuid 0 -app_vitals metric_name text 0 -app_vitals metric_value numeric 0 -app_vitals rating text 0 -app_vitals request_id text 0 -app_vitals page_url text 0 -app_vitals user_agent text 0 -app_vitals user_id uuid 0 -app_vitals created_at timestamp with time zone 0 -art_file_attachments id uuid 0 -art_file_attachments user_id uuid 0 -art_file_attachments mockup_id uuid 0 -art_file_attachments quote_id uuid 0 -art_file_attachments file_url text 0 -art_file_attachments file_path text 0 -art_file_attachments original_name text 0 -art_file_attachments mime_type text 0 -art_file_attachments file_size_bytes bigint 0 -art_file_attachments file_extension text 0 -art_file_attachments notes text 0 -art_file_attachments created_at timestamp with time zone 0 -art_file_attachments updated_at timestamp with time zone 0 -attribute_definitions id uuid 0 -attribute_definitions organization_id uuid 0 -attribute_definitions group_id uuid 0 -attribute_definitions name character varying 0 -attribute_definitions slug character varying 0 -attribute_definitions display_name character varying 0 -attribute_definitions description text 0 -attribute_definitions data_type character varying 0 -attribute_definitions unit character varying 0 -attribute_definitions default_value text 0 -attribute_definitions possible_values jsonb 0 -attribute_definitions validation_rules jsonb 0 -attribute_definitions is_required boolean 0 -attribute_definitions is_filterable boolean 0 -attribute_definitions is_comparable boolean 0 -attribute_definitions is_visible boolean 0 -attribute_definitions is_searchable boolean 0 -attribute_definitions display_order integer 0 -attribute_definitions is_active boolean 0 -attribute_definitions created_at timestamp with time zone 0 -attribute_definitions updated_at timestamp with time zone 0 -attribute_equivalences id uuid 0 -attribute_equivalences organization_id uuid 0 -attribute_equivalences promo_attribute_id uuid 0 -attribute_equivalences supplier_attribute_id uuid 0 -attribute_equivalences value_transformation jsonb 0 -attribute_equivalences match_quality character varying 0 -attribute_equivalences confidence_score integer 0 -attribute_equivalences is_active boolean 0 -attribute_equivalences verified boolean 0 -attribute_equivalences verified_by uuid 0 -attribute_equivalences verified_at timestamp with time zone 0 -attribute_equivalences source character varying 0 -attribute_equivalences notes text 0 -attribute_equivalences created_at timestamp with time zone 0 -attribute_equivalences updated_at timestamp with time zone 0 -attribute_groups id uuid 0 -attribute_groups organization_id uuid 0 -attribute_groups name character varying 0 -attribute_groups slug character varying 0 -attribute_groups description text 0 -attribute_groups icon character varying 0 -attribute_groups display_order integer 0 -attribute_groups is_active boolean 0 -attribute_groups created_at timestamp with time zone 0 -attribute_groups updated_at timestamp with time zone 0 -audit_log id uuid 0 -audit_log user_id uuid 0 -audit_log action text 0 -audit_log entity_type text 0 -audit_log entity_id uuid 0 -audit_log old_values jsonb 0 -audit_log new_values jsonb 0 -audit_log ip_address inet 0 -audit_log user_agent text 0 -audit_log created_at timestamp with time zone 0 -audit_log_gravacao id bigint 0 -audit_log_gravacao tabela_origem text 0 -audit_log_gravacao operacao text 0 -audit_log_gravacao registro_id uuid 0 -audit_log_gravacao codigo_tabela text 0 -audit_log_gravacao campos_alterados jsonb 0 -audit_log_gravacao valor_antes jsonb 0 -audit_log_gravacao valor_depois jsonb 0 -audit_log_gravacao usuario text 0 -audit_log_gravacao ts timestamp with time zone 0 -audit_logs id uuid 0 -audit_logs event_type text 0 -audit_logs endpoint text 0 -audit_logs identifier text 0 -audit_logs metadata jsonb 0 -audit_logs created_at timestamp with time zone 0 -auth_login_attempts id uuid 0 -auth_login_attempts email text 0 -auth_login_attempts ip_address text 0 -auth_login_attempts success boolean 0 -auth_login_attempts failure_reason text 0 -auth_login_attempts user_agent text 0 -auth_login_attempts created_at timestamp with time zone 0 -auto_tag_rules id uuid 0 -auto_tag_rules organization_id uuid 0 -auto_tag_rules rule_name character varying 0 -auto_tag_rules rule_type character varying 0 -auto_tag_rules condition_field character varying 0 -auto_tag_rules condition_operator character varying 0 -auto_tag_rules condition_value text 0 -auto_tag_rules condition_value_max text 0 -auto_tag_rules tag_slug character varying 0 -auto_tag_rules priority integer 0 -auto_tag_rules is_active boolean 0 -auto_tag_rules created_at timestamp with time zone 0 -auto_tag_rules updated_at timestamp with time zone 0 -b2b_collection_products id uuid 0 -b2b_collection_products b2b_collection_id uuid 0 -b2b_collection_products product_id uuid 0 -b2b_collection_products display_order integer 0 -b2b_collection_products added_at timestamp with time zone 0 -b2b_collections id uuid 0 -b2b_collections name text 0 -b2b_collections slug text 0 -b2b_collections description text 0 -b2b_collections image_url text 0 -b2b_collections is_public boolean 0 -b2b_collections is_featured boolean 0 -b2b_collections display_order integer 0 -b2b_collections created_by uuid 0 -b2b_collections created_at timestamp with time zone 0 -b2b_collections updated_at timestamp with time zone 0 -b2b_collections organization_id uuid 0 -bot_detection_log id uuid 0 -bot_detection_log ip_address text 0 -bot_detection_log user_agent text 0 -bot_detection_log detection_type text 0 -bot_detection_log score numeric 0 -bot_detection_log action_taken text 0 -bot_detection_log metadata jsonb 0 -bot_detection_log created_at timestamp with time zone 0 -bot_detection_log blocked boolean 0 -cart_templates id uuid 0 -cart_templates user_id uuid 0 -cart_templates name text 0 -cart_templates description text 0 -cart_templates items jsonb 0 -cart_templates created_at timestamp with time zone 0 -cart_templates updated_at timestamp with time zone 0 -categories id uuid 0 -categories name text 0 -categories icon text 0 -categories description text 0 -categories parent_id uuid 0 -categories sort_order integer 0 -categories active boolean 0 -categories created_at timestamp with time zone 0 -categories slug text 0 -categories image_url text 0 -categories display_order integer 0 -categories is_active boolean 0 -categories updated_at timestamp with time zone 0 -categories organization_id uuid 0 -categories bitrix_id integer 0 -categories bitrix_modified_at timestamp with time zone 0 -categories synced_at timestamp with time zone 0 -categories sync_status text 0 -categories level integer 0 -categories path text 0 -categories is_visible boolean 0 -categories color_hex text 0 -categories meta_title text 0 -categories meta_description text 0 -categories meta_keywords ARRAY 0 -categories products_count integer 0 -categories children_count integer 0 -categories descendants_count integer 0 -categories created_by uuid 0 -categories updated_by uuid 0 -categories deleted_at timestamp with time zone 0 -categories full_path_readable text 0 -categories ai_summary text 0 -categories schema_json jsonb 0 -categories seo_priority numeric 0 -categories min_order_quantity integer 0 -category_accessory_categories id uuid 0 -category_accessory_categories category_id uuid 0 -category_accessory_categories accessory_category_id uuid 0 -category_accessory_categories relationship_type character varying 0 -category_accessory_categories is_required boolean 0 -category_accessory_categories include_children boolean 0 -category_accessory_categories display_order integer 0 -category_accessory_categories display_label character varying 0 -category_accessory_categories is_active boolean 0 -category_accessory_categories created_at timestamp with time zone 0 -category_accessory_categories updated_at timestamp with time zone 0 -category_attributes id uuid 0 -category_attributes category_id uuid 0 -category_attributes attribute_key text 0 -category_attributes attribute_value text 0 -category_attributes attribute_type text 0 -category_attributes is_filterable boolean 0 -category_attributes display_order integer 0 -category_attributes created_at timestamp with time zone 0 -category_attributes updated_at timestamp with time zone 0 -category_colors id uuid 0 -category_colors category_id uuid 0 -category_colors color_group_id uuid 0 -category_colors is_primary boolean 0 -category_colors display_order integer 0 -category_colors is_active boolean 0 -category_colors created_at timestamp with time zone 0 -category_colors updated_at timestamp with time zone 0 -category_commemorative_dates id uuid 0 -category_commemorative_dates category_id uuid 0 -category_commemorative_dates commemorative_date_id uuid 0 -category_commemorative_dates is_primary boolean 0 -category_commemorative_dates relevance_score integer 0 -category_commemorative_dates display_order integer 0 -category_commemorative_dates custom_message text 0 -category_commemorative_dates is_active boolean 0 -category_commemorative_dates created_at timestamp with time zone 0 -category_commemorative_dates updated_at timestamp with time zone 0 -category_copywriting_config id uuid 0 -category_copywriting_config category_id uuid 0 -category_copywriting_config personas ARRAY 0 -category_copywriting_config ocasioes ARRAY 0 -category_copywriting_config keywords_seo ARRAY 0 -category_copywriting_config tom_voz character varying 0 -category_copywriting_config ctas ARRAY 0 -category_copywriting_config storytelling_template text 0 -category_copywriting_config diferenciais ARRAY 0 -category_copywriting_config beneficios_emocionais ARRAY 0 -category_copywriting_config objecoes_comuns ARRAY 0 -category_copywriting_config unidade_peso character varying 0 -category_copywriting_config unidade_capacidade character varying 0 -category_copywriting_config unidade_dimensao character varying 0 -category_copywriting_config config_extra jsonb 0 -category_copywriting_config is_active boolean 0 -category_copywriting_config created_at timestamp with time zone 0 -category_copywriting_config updated_at timestamp with time zone 0 -category_copywriting_config created_by uuid 0 -category_copywriting_config updated_by uuid 0 -category_icons id uuid 0 -category_icons category_name text 0 -category_icons icon text 0 -category_icons description text 0 -category_icons is_active boolean 0 -category_icons created_at timestamp with time zone 0 -category_icons updated_at timestamp with time zone 0 -category_relationships id uuid 0 -category_relationships organization_id uuid 0 -category_relationships parent_id uuid 0 -category_relationships child_id uuid 0 -category_relationships parent_bitrix_id integer 0 -category_relationships child_bitrix_id integer 0 -category_relationships parent_name text 0 -category_relationships child_name text 0 -category_relationships depth integer 0 -category_relationships created_at timestamp with time zone 0 -category_target_audiences id uuid 0 -category_target_audiences category_id uuid 0 -category_target_audiences target_audience_id uuid 0 -category_target_audiences is_primary boolean 0 -category_target_audiences relevance_score integer 0 -category_target_audiences display_order integer 0 -category_target_audiences is_active boolean 0 -category_target_audiences created_at timestamp with time zone 0 -category_target_audiences updated_at timestamp with time zone 0 -category_variation_types id uuid 0 -category_variation_types category_id uuid 0 -category_variation_types variation_type_id uuid 0 -category_variation_types is_required boolean 0 -category_variation_types inherit_from_parent boolean 0 -category_variation_types display_order integer 0 -category_variation_types is_active boolean 0 -category_variation_types created_at timestamp with time zone 0 -category_variation_types updated_at timestamp with time zone 0 -classify_functions_registry id integer 0 -classify_functions_registry function_name character varying 0 -classify_functions_registry description text 0 -classify_functions_registry is_field character varying 0 -classify_functions_registry priority integer 0 -classify_functions_registry is_active boolean 0 -classify_functions_registry created_at timestamp with time zone 0 -classify_functions_registry updated_at timestamp with time zone 0 -collection_item_reactions id uuid 0 -collection_item_reactions collection_id uuid 0 -collection_item_reactions item_id uuid 0 -collection_item_reactions anon_id text 0 -collection_item_reactions emoji text 0 -collection_item_reactions ip_hash text 0 -collection_item_reactions user_agent text 0 -collection_item_reactions created_at timestamp with time zone 0 -collection_items id uuid 0 -collection_items collection_id uuid 0 -collection_items product_id text 0 -collection_items color_name text 0 -collection_items color_hex text 0 -collection_items thumbnail_url text 0 -collection_items sort_order integer 0 -collection_items created_at timestamp with time zone 0 -collection_items notes text 0 -collection_items price_at_save numeric 0 -collection_items added_at timestamp with time zone 0 -collection_items_trash id uuid 0 -collection_items_trash original_id uuid 0 -collection_items_trash collection_id uuid 0 -collection_items_trash user_id uuid 0 -collection_items_trash product_id text 0 -collection_items_trash color_name text 0 -collection_items_trash color_hex text 0 -collection_items_trash thumbnail_url text 0 -collection_items_trash notes text 0 -collection_items_trash price_at_save numeric 0 -collection_items_trash sort_order integer 0 -collection_items_trash deleted_at timestamp with time zone 0 -collection_items_trash expires_at timestamp with time zone 0 -collection_products id uuid 0 -collection_products collection_id uuid 0 -collection_products product_id uuid 0 -collection_products display_order integer 0 -collection_products created_at timestamp with time zone 0 -collections id uuid 0 -collections user_id uuid 0 -collections name text 0 -collections description text 0 -collections is_featured boolean 0 -collections icon_color text 0 -collections created_at timestamp with time zone 0 -collections updated_at timestamp with time zone 0 -collections icon text 0 -collections client_id text 0 -collections client_name text 0 -collections share_token text 0 -collections share_expires_at timestamp with time zone 0 -collections is_public boolean 0 -collections is_deleted boolean 0 -color_analysis_staging id uuid 0 -color_analysis_staging variant_id uuid 0 -color_analysis_staging product_sku text 0 -color_analysis_staging variant_sku text 0 -color_analysis_staging image_url text 0 -color_analysis_staging supplier_color_name text 0 -color_analysis_staging detected_hex text 0 -color_analysis_staging detected_rgb_r integer 0 -color_analysis_staging detected_rgb_g integer 0 -color_analysis_staging detected_rgb_b integer 0 -color_analysis_staging blue_pixel_count integer 0 -color_analysis_staging total_pixel_count integer 0 -color_analysis_staging current_variation_id uuid 0 -color_analysis_staging current_variation_name text 0 -color_analysis_staging suggested_variation_id uuid 0 -color_analysis_staging suggested_variation_name text 0 -color_analysis_staging suggested_hex text 0 -color_analysis_staging match_distance numeric 0 -color_analysis_staging needs_review boolean 0 -color_analysis_staging reviewed boolean 0 -color_analysis_staging approved boolean 0 -color_analysis_staging processed_at timestamp with time zone 0 -color_analysis_staging organization_id uuid 0 -color_equivalences id uuid 0 -color_equivalences organization_id uuid 0 -color_equivalences promo_variation_id uuid 0 -color_equivalences promo_nuance_id uuid 0 -color_equivalences supplier_color_id uuid 0 -color_equivalences match_quality character varying 0 -color_equivalences confidence_score integer 0 -color_equivalences is_active boolean 0 -color_equivalences verified boolean 0 -color_equivalences verified_by uuid 0 -color_equivalences verified_at timestamp with time zone 0 -color_equivalences source character varying 0 -color_equivalences notes text 0 -color_equivalences created_at timestamp with time zone 0 -color_equivalences updated_at timestamp with time zone 0 -color_groups id uuid 0 -color_groups organization_id uuid 0 -color_groups name character varying 0 -color_groups slug character varying 0 -color_groups description text 0 -color_groups hex_code character varying 0 -color_groups bitrix_id integer 0 -color_groups sort_order integer 0 -color_groups is_active boolean 0 -color_groups created_at timestamp with time zone 0 -color_groups updated_at timestamp with time zone 0 -color_groups internal_code character varying 0 -color_nuances id uuid 0 -color_nuances organization_id uuid 0 -color_nuances name character varying 0 -color_nuances slug character varying 0 -color_nuances description text 0 -color_nuances bitrix_id integer 0 -color_nuances sort_order integer 0 -color_nuances is_active boolean 0 -color_nuances created_at timestamp with time zone 0 -color_nuances updated_at timestamp with time zone 0 -color_variations id uuid 0 -color_variations organization_id uuid 0 -color_variations group_id uuid 0 -color_variations name character varying 0 -color_variations slug character varying 0 -color_variations hex_code character varying 0 -color_variations bitrix_id integer 0 -color_variations xml_id character varying 0 -color_variations image_url text 0 -color_variations description text 0 -color_variations sort_order integer 0 -color_variations is_active boolean 0 -color_variations is_available boolean 0 -color_variations created_at timestamp with time zone 0 -color_variations updated_at timestamp with time zone 0 -color_variations color_group_id uuid 0 -color_variations nuance_id uuid 0 -color_variations internal_code character varying 0 -commemorative_date_colors id uuid 0 -commemorative_date_colors commemorative_date_id uuid 0 -commemorative_date_colors color_group_id uuid 0 -commemorative_date_colors is_primary boolean 0 -commemorative_date_colors match_score integer 0 -commemorative_date_colors created_at timestamp with time zone 0 -commemorative_date_exclusions id uuid 0 -commemorative_date_exclusions commemorative_date_id uuid 0 -commemorative_date_exclusions product_id uuid 0 -commemorative_date_exclusions variant_id uuid 0 -commemorative_date_exclusions reason text 0 -commemorative_date_exclusions excluded_by uuid 0 -commemorative_date_exclusions created_at timestamp with time zone 0 -commemorative_dates id uuid 0 -commemorative_dates name text 0 -commemorative_dates slug text 0 -commemorative_dates description text 0 -commemorative_dates date_day integer 0 -commemorative_dates date_month integer 0 -commemorative_dates is_fixed_date boolean 0 -commemorative_dates variable_date_rule text 0 -commemorative_dates category text 0 -commemorative_dates target_audience text 0 -commemorative_dates icon_name text 0 -commemorative_dates color_hex text 0 -commemorative_dates image_url text 0 -commemorative_dates banner_url text 0 -commemorative_dates is_active boolean 0 -commemorative_dates is_featured boolean 0 -commemorative_dates display_order integer 0 -commemorative_dates campaign_start_days integer 0 -commemorative_dates campaign_end_days integer 0 -commemorative_dates organization_id uuid 0 -commemorative_dates created_by uuid 0 -commemorative_dates created_at timestamp with time zone 0 -commemorative_dates updated_by uuid 0 -commemorative_dates updated_at timestamp with time zone 0 -company_email_patterns id uuid 0 -company_email_patterns company_domain text 0 -company_email_patterns company_name text 0 -company_email_patterns pattern_format text 0 -company_email_patterns pattern_confidence smallint 0 -company_email_patterns mx_server text 0 -company_email_patterns email_provider text 0 -company_email_patterns is_catch_all boolean 0 -company_email_patterns verified_count integer 0 -company_email_patterns last_verified_at timestamp with time zone 0 -company_email_patterns created_at timestamp with time zone 0 -company_email_patterns updated_at timestamp with time zone 0 -comparison_reactions id uuid 0 -comparison_reactions comparison_id uuid 0 -comparison_reactions item_index integer 0 -comparison_reactions emoji text 0 -comparison_reactions anon_id text 0 -comparison_reactions ip_hash text 0 -comparison_reactions user_agent text 0 -comparison_reactions created_at timestamp with time zone 0 -component_media id uuid 0 -component_media component_id text 0 -component_media product_id text 0 -component_media media_type text 0 -component_media url text 0 -component_media title text 0 -component_media sort_order integer 0 -component_media is_cover boolean 0 -component_media created_at timestamp with time zone 0 -component_media updated_at timestamp with time zone 0 -connection_test_history id uuid 0 -connection_test_history connection_id uuid 0 -connection_test_history tested_at timestamp with time zone 0 -connection_test_history success boolean 0 -connection_test_history latency_ms integer 0 -connection_test_history status_code integer 0 -connection_test_history error_message text 0 -connection_test_history created_at timestamp with time zone 0 -connection_test_history triggered_by text 0 -connection_test_history error_kind text 0 -connection_test_history request_method text 0 -connection_test_history request_url text 0 -connection_test_history response_headers jsonb 0 -connection_test_history response_body text 0 -connection_test_history dns_ms integer 0 -connection_test_history tcp_ms integer 0 -connection_test_history tls_ms integer 0 -connection_test_history ttfb_ms integer 0 -connection_test_history download_ms integer 0 -connection_test_history triggered_by_user_id uuid 0 -connection_test_history attempts smallint 0 -conversation_audit_logs id uuid 0 -conversation_audit_logs session_id text 0 -conversation_audit_logs user_id uuid 0 -conversation_audit_logs started_at timestamp with time zone 0 -conversation_audit_logs ended_at timestamp with time zone 0 -conversation_audit_logs total_tokens_estimated integer 0 -conversation_audit_logs metadata jsonb 0 -conversation_audit_logs status text 0 -conversation_audit_logs client_info jsonb 0 -conversation_delivery_status id uuid 0 -conversation_delivery_status event_id uuid 0 -conversation_delivery_status status text 0 -conversation_delivery_status error_details text 0 -conversation_delivery_status updated_at timestamp with time zone 0 -conversation_event_history id uuid 0 -conversation_event_history conversation_id uuid 0 -conversation_event_history role text 0 -conversation_event_history event_type USER-DEFINED 0 -conversation_event_history content text 0 -conversation_event_history media_url text 0 -conversation_event_history media_metadata jsonb 0 -conversation_event_history tokens_estimated integer 0 -conversation_event_history created_at timestamp with time zone 0 -conversation_event_history request_id uuid 0 -custom_kits id uuid 0 -custom_kits organization_id uuid 0 -custom_kits name text 0 -custom_kits total_price numeric 0 -custom_kits personalization_price numeric 0 -custom_kits kit_quantity integer 0 -custom_kits status text 0 -custom_kits created_at timestamp with time zone 0 -custom_kits updated_at timestamp with time zone 0 -custom_kits user_id uuid 0 -custom_kits kit_type text 0 -custom_kits box_data jsonb 0 -custom_kits items_data jsonb 0 -custom_kits personalization_data jsonb 0 -custom_kits box_price numeric 0 -custom_kits items_price numeric 0 -custom_kits volume_usage_percent numeric 0 -custom_kits color text 0 -custom_kits icon text 0 -custom_kits tag text 0 -custom_kits description text 0 -custom_kits is_favorite boolean 0 -custom_kits is_pinned boolean 0 -custom_kits last_used_at timestamp with time zone 0 -discount_approval_requests id uuid 0 -discount_approval_requests quote_id uuid 0 -discount_approval_requests seller_id uuid 0 -discount_approval_requests requested_discount_percent numeric 0 -discount_approval_requests max_allowed_percent numeric 0 -discount_approval_requests status text 0 -discount_approval_requests admin_id uuid 0 -discount_approval_requests admin_notes text 0 -discount_approval_requests seller_notes text 0 -discount_approval_requests responded_at timestamp with time zone 0 -discount_approval_requests created_at timestamp with time zone 0 -discount_approval_requests updated_at timestamp with time zone 0 -discount_approval_requests valid_until timestamp with time zone 0 -discount_approval_requests quote_snapshot_hash text 0 -e2e_cleanup_rate_limit key text 0 -e2e_cleanup_rate_limit count integer 0 -e2e_cleanup_rate_limit window_start timestamp with time zone 0 -e2e_cleanup_rate_limit updated_at timestamp with time zone 0 -eco_material_config material_id uuid 0 -eco_material_config material_name character varying 0 -eco_material_config notes text 0 -eco_material_config created_at timestamp with time zone 0 -eco_material_config is_active boolean 0 -edge_function_invocations id uuid 0 -edge_function_invocations function_slug text 0 -edge_function_invocations invoked_by uuid 0 -edge_function_invocations invoked_at timestamp with time zone 0 -edge_function_invocations request_method text 0 -edge_function_invocations request_metadata jsonb 0 -edge_function_invocations status_code integer 0 -edge_function_invocations duration_ms integer 0 -edge_function_invocations error_message text 0 -edge_function_invocations ip_address inet 0 -edge_function_invocations user_agent text 0 -edge_rate_limits key text 0 -edge_rate_limits count integer 0 -edge_rate_limits reset_at timestamp with time zone 0 -edge_rate_limits created_at timestamp with time zone 0 -edge_rate_limits updated_at timestamp with time zone 0 -enriched_contacts id uuid 0 -enriched_contacts linkedin_url text 0 -enriched_contacts linkedin_profile_id text 0 -enriched_contacts first_name text 0 -enriched_contacts last_name text 0 -enriched_contacts full_name text 0 -enriched_contacts headline text 0 -enriched_contacts current_title text 0 -enriched_contacts current_company text 0 -enriched_contacts company_domain text 0 -enriched_contacts location text 0 -enriched_contacts profile_picture_url text 0 -enriched_contacts work_email text 0 -enriched_contacts work_email_confidence smallint 0 -enriched_contacts work_email_verified_at timestamp with time zone 0 -enriched_contacts work_email_source text 0 -enriched_contacts personal_email text 0 -enriched_contacts personal_email_confidence smallint 0 -enriched_contacts personal_email_source text 0 -enriched_contacts phone_mobile text 0 -enriched_contacts phone_mobile_confidence smallint 0 -enriched_contacts phone_mobile_source text 0 -enriched_contacts phone_work text 0 -enriched_contacts phone_work_confidence smallint 0 -enriched_contacts phone_work_source text 0 -enriched_contacts whatsapp_number text 0 -enriched_contacts company_size text 0 -enriched_contacts company_industry text 0 -enriched_contacts company_linkedin_url text 0 -enriched_contacts company_website text 0 -enriched_contacts enrichment_status text 0 -enriched_contacts enrichment_attempts integer 0 -enriched_contacts last_enriched_at timestamp with time zone 0 -enriched_contacts bitrix24_contact_id integer 0 -enriched_contacts bitrix24_synced_at timestamp with time zone 0 -enriched_contacts created_at timestamp with time zone 0 -enriched_contacts updated_at timestamp with time zone 0 -enriched_contacts created_by text 0 -enrichment_log id uuid 0 -enrichment_log contact_id uuid 0 -enrichment_log enrichment_level integer 0 -enrichment_log provider_name text 0 -enrichment_log success boolean 0 -enrichment_log data_found jsonb 0 -enrichment_log error_message text 0 -enrichment_log credits_used numeric 0 -enrichment_log cost_usd numeric 0 -enrichment_log response_time_ms integer 0 -enrichment_log created_at timestamp with time zone 0 -expert_conversations id uuid 0 -expert_conversations seller_id uuid 0 -expert_conversations client_id text 0 -expert_conversations title text 0 -expert_conversations created_at timestamp with time zone 0 -expert_conversations updated_at timestamp with time zone 0 -expert_messages id uuid 0 -expert_messages conversation_id uuid 0 -expert_messages role text 0 -expert_messages content text 0 -expert_messages created_at timestamp with time zone 0 -external_connections id uuid 0 -external_connections type text 0 -external_connections name text 0 -external_connections config jsonb 0 -external_connections secret_refs ARRAY 0 -external_connections status text 0 -external_connections last_test_at timestamp with time zone 0 -external_connections last_test_ok boolean 0 -external_connections last_test_message text 0 -external_connections created_by uuid 0 -external_connections created_at timestamp with time zone 0 -external_connections updated_at timestamp with time zone 0 -external_connections last_latency_ms integer 0 -external_connections env_key text 0 -external_connections auto_test_enabled boolean 0 -external_connections_sync_log id uuid 0 -external_connections_sync_log connection_id uuid 0 -external_connections_sync_log connection_name text 0 -external_connections_sync_log sync_type text 0 -external_connections_sync_log status text 0 -external_connections_sync_log records_processed integer 0 -external_connections_sync_log records_failed integer 0 -external_connections_sync_log duration_ms integer 0 -external_connections_sync_log error_message text 0 -external_connections_sync_log metadata jsonb 0 -external_connections_sync_log started_at timestamp with time zone 0 -external_connections_sync_log completed_at timestamp with time zone 0 -favorite_item_reactions id uuid 0 -favorite_item_reactions item_id uuid 0 -favorite_item_reactions list_id uuid 0 -favorite_item_reactions anon_id text 0 -favorite_item_reactions emoji text 0 -favorite_item_reactions ip_hash text 0 -favorite_item_reactions user_agent text 0 -favorite_item_reactions created_at timestamp with time zone 0 -favorite_items id uuid 0 -favorite_items list_id uuid 0 -favorite_items user_id uuid 0 -favorite_items product_id uuid 0 -favorite_items variant_id uuid 0 -favorite_items variant_info jsonb 0 -favorite_items note text 0 -favorite_items price_at_save numeric 0 -favorite_items position integer 0 -favorite_items added_at timestamp with time zone 0 -favorite_items updated_at timestamp with time zone 0 -favorite_items_trash id uuid 0 -favorite_items_trash original_id uuid 0 -favorite_items_trash list_id uuid 0 -favorite_items_trash user_id uuid 0 -favorite_items_trash product_id uuid 0 -favorite_items_trash variant_id uuid 0 -favorite_items_trash variant_info jsonb 0 -favorite_items_trash note text 0 -favorite_items_trash price_at_save numeric 0 -favorite_items_trash position integer 0 -favorite_items_trash added_at timestamp with time zone 0 -favorite_items_trash deleted_at timestamp with time zone 0 -favorite_items_trash expires_at timestamp with time zone 0 -favorite_lists id uuid 0 -favorite_lists user_id uuid 0 -favorite_lists name text 0 -favorite_lists description text 0 -favorite_lists color text 0 -favorite_lists icon text 0 -favorite_lists is_default boolean 0 -favorite_lists is_archived boolean 0 -favorite_lists client_id uuid 0 -favorite_lists client_name text 0 -favorite_lists shared_token text 0 -favorite_lists shared_expires_at timestamp with time zone 0 -favorite_lists position integer 0 -favorite_lists created_at timestamp with time zone 0 -favorite_lists updated_at timestamp with time zone 0 -feminine_color_config color_group_id uuid 0 -feminine_color_config color_group_name character varying 0 -feminine_color_config notes text 0 -feminine_color_config created_at timestamp with time zone 0 -feminine_color_config is_active boolean 0 -file_scan_logs id uuid 0 -file_scan_logs user_id uuid 0 -file_scan_logs file_name text 0 -file_scan_logs file_size bigint 0 -file_scan_logs file_hash text 0 -file_scan_logs mime_type text 0 -file_scan_logs scan_provider text 0 -file_scan_logs scan_result text 0 -file_scan_logs scan_response jsonb 0 -file_scan_logs storage_path text 0 -file_scan_logs created_at timestamp with time zone 0 -follow_up_reminders id uuid 0 -follow_up_reminders quote_id text 0 -follow_up_reminders seller_id uuid 0 -follow_up_reminders reminder_type text 0 -follow_up_reminders scheduled_for timestamp with time zone 0 -follow_up_reminders is_sent boolean 0 -follow_up_reminders sent_at timestamp with time zone 0 -follow_up_reminders created_at timestamp with time zone 0 -follow_up_reminders title text 0 -follow_up_reminders notes text 0 -follow_up_reminders is_completed boolean 0 -follow_up_reminders completed_at timestamp with time zone 0 -frontend_telemetry id uuid 0 -frontend_telemetry event_type text 0 -frontend_telemetry name text 0 -frontend_telemetry duration_ms double precision 0 -frontend_telemetry metadata jsonb 0 -frontend_telemetry url text 0 -frontend_telemetry user_agent text 0 -frontend_telemetry session_id text 0 -frontend_telemetry user_id uuid 0 -frontend_telemetry created_at timestamp with time zone 0 -generated_mockups id uuid 0 -generated_mockups job_id uuid 0 -generated_mockups user_id uuid 0 -generated_mockups product_id uuid 0 -generated_mockups product_name text 0 -generated_mockups product_sku text 0 -generated_mockups technique_id uuid 0 -generated_mockups technique_name text 0 -generated_mockups product_color_hex text 0 -generated_mockups product_color_name text 0 -generated_mockups area_name text 0 -generated_mockups area_config jsonb 0 -generated_mockups mockup_url text 0 -generated_mockups logo_url text 0 -generated_mockups thumbnail_url text 0 -generated_mockups ai_model_used text 0 -generated_mockups generation_time_seconds integer 0 -generated_mockups prompt_used text 0 -generated_mockups seed_used integer 0 -generated_mockups quality_score numeric 0 -generated_mockups has_errors boolean 0 -generated_mockups error_details text 0 -generated_mockups approval_status text 0 -generated_mockups approved_by_user_id uuid 0 -generated_mockups approved_at timestamp with time zone 0 -generated_mockups client_feedback text 0 -generated_mockups generation_cost numeric 0 -generated_mockups created_at timestamp with time zone 0 -generated_mockups updated_at timestamp with time zone 0 -geo_allowed_countries id uuid 0 -geo_allowed_countries country_code character 0 -geo_allowed_countries country_name text 0 -geo_allowed_countries is_active boolean 0 -geo_allowed_countries created_at timestamp with time zone 0 -geo_allowed_countries created_by uuid 0 -hardening_health_snapshots id uuid 0 -hardening_health_snapshots snapshot_at timestamp with time zone 0 -hardening_health_snapshots score integer 0 -hardening_health_snapshots max_score integer 0 -hardening_health_snapshots failures ARRAY 0 -hardening_health_snapshots details jsonb 0 -hardening_health_snapshots created_at timestamp with time zone 0 -image_import_log id uuid 0 -image_import_log cloudflare_image_id character varying 0 -image_import_log product_id uuid 0 -image_import_log source_type character varying 0 -image_import_log source_zip character varying 0 -image_import_log source_url text 0 -image_import_log parsed_sku character varying 0 -image_import_log parsed_color_code character varying 0 -image_import_log supplier_ref_id character varying 0 -image_import_log sha256_hash text 0 -image_import_log import_status character varying 0 -image_import_log error_message text 0 -image_import_log original_created_at timestamp with time zone 0 -image_import_log imported_at timestamp with time zone 0 -image_types id uuid 0 -image_types code character varying 0 -image_types name character varying 0 -image_types name_en character varying 0 -image_types description text 0 -image_types description_en text 0 -image_types category character varying 0 -image_types subcategory character varying 0 -image_types is_color_specific boolean 0 -image_types is_primary_candidate boolean 0 -image_types requires_variant boolean 0 -image_types show_in_gallery boolean 0 -image_types show_in_simulator boolean 0 -image_types is_internal_only boolean 0 -image_types display_priority smallint 0 -image_types gallery_order smallint 0 -image_types icon character varying 0 -image_types ui_color character varying 0 -image_types is_active boolean 0 -image_types created_at timestamp with time zone 0 -image_types updated_at timestamp with time zone 0 -image_validation_log id uuid 0 -image_validation_log image_id uuid 0 -image_validation_log product_id uuid 0 -image_validation_log filename character varying 0 -image_validation_log supplier_code character varying 0 -image_validation_log width_px integer 0 -image_validation_log height_px integer 0 -image_validation_log file_size_bytes bigint 0 -image_validation_log validation_status character varying 0 -image_validation_log is_valid boolean 0 -image_validation_log issues ARRAY 0 -image_validation_log recommendations ARRAY 0 -image_validation_log action_taken character varying 0 -image_validation_log was_blocked boolean 0 -image_validation_log validated_at timestamp with time zone 0 -import_pipeline_steps id integer 0 -import_pipeline_steps fase character varying 0 -import_pipeline_steps etapa integer 0 -import_pipeline_steps titulo character varying 0 -import_pipeline_steps descricao text 0 -import_pipeline_steps comando_resumo text 0 -import_pipeline_steps dependencia ARRAY 0 -import_pipeline_steps status character varying 0 -import_pipeline_steps resultado jsonb 0 -import_pipeline_steps error_message text 0 -import_pipeline_steps started_at timestamp with time zone 0 -import_pipeline_steps completed_at timestamp with time zone 0 -import_pipeline_steps created_at timestamp with time zone 0 -import_pipeline_steps updated_at timestamp with time zone 0 -import_staging_images id uuid 0 -import_staging_images filename character varying 0 -import_staging_images source_zip character varying 0 -import_staging_images source_folder character varying 0 -import_staging_images file_size_bytes bigint 0 -import_staging_images parsed_sku character varying 0 -import_staging_images parsed_color_code character varying 0 -import_staging_images parsed_component integer 0 -import_staging_images parsed_location integer 0 -import_staging_images parsed_view integer 0 -import_staging_images product_id uuid 0 -import_staging_images color_id uuid 0 -import_staging_images variant_id uuid 0 -import_staging_images cloudflare_image_id character varying 0 -import_staging_images url_cdn text 0 -import_staging_images target_image_type_id uuid 0 -import_staging_images target_image_type character varying 0 -import_staging_images target_display_order integer 0 -import_staging_images status character varying 0 -import_staging_images batch_id character varying 0 -import_staging_images retry_count integer 0 -import_staging_images error_message text 0 -import_staging_images product_image_id uuid 0 -import_staging_images created_at timestamp with time zone 0 -import_staging_images updated_at timestamp with time zone 0 -import_staging_images processed_at timestamp with time zone 0 -import_staging_images uploaded_at timestamp with time zone 0 -import_staging_images completed_at timestamp with time zone 0 -inbound_webhook_endpoints id uuid 0 -inbound_webhook_endpoints slug text 0 -inbound_webhook_endpoints name text 0 -inbound_webhook_endpoints description text 0 -inbound_webhook_endpoints secret_key text 0 -inbound_webhook_endpoints is_active boolean 0 -inbound_webhook_endpoints allowed_ips ARRAY 0 -inbound_webhook_endpoints metadata jsonb 0 -inbound_webhook_endpoints created_by uuid 0 -inbound_webhook_endpoints created_at timestamp with time zone 0 -inbound_webhook_endpoints updated_at timestamp with time zone 0 -inbound_webhook_endpoints source_system text 0 -inbound_webhook_endpoints hmac_secret_ref text 0 -inbound_webhook_endpoints allowed_events ARRAY 0 -inbound_webhook_endpoints last_received_at timestamp with time zone 0 -inbound_webhook_endpoints total_received integer 0 -inbound_webhook_endpoints total_invalid integer 0 -inbound_webhook_events id uuid 0 -inbound_webhook_events endpoint_id uuid 0 -inbound_webhook_events event_type text 0 -inbound_webhook_events payload jsonb 0 -inbound_webhook_events headers jsonb 0 -inbound_webhook_events ip_address text 0 -inbound_webhook_events signature_valid boolean 0 -inbound_webhook_events processed boolean 0 -inbound_webhook_events processed_at timestamp with time zone 0 -inbound_webhook_events error_message text 0 -inbound_webhook_events created_at timestamp with time zone 0 -integration_credentials id uuid 0 -integration_credentials provider text 0 -integration_credentials secret_name text 0 -integration_credentials description text 0 -integration_credentials credential_type text 0 -integration_credentials secret_value text 0 -integration_credentials metadata jsonb 0 -integration_credentials is_active boolean 0 -integration_credentials expires_at timestamp with time zone 0 -integration_credentials created_by uuid 0 -integration_credentials created_at timestamp with time zone 0 -integration_credentials updated_at timestamp with time zone 0 -integration_credentials notes text 0 -integration_credentials updated_by uuid 0 -integration_credentials masked_suffix text 0 -integration_credentials length integer 0 -ip_access_control id uuid 0 -ip_access_control ip_address text 0 -ip_access_control list_type text 0 -ip_access_control reason text 0 -ip_access_control expires_at timestamp with time zone 0 -ip_access_control created_by uuid 0 -ip_access_control metadata jsonb 0 -ip_access_control created_at timestamp with time zone 0 -ip_access_control updated_at timestamp with time zone 0 -kit_collaborators id uuid 0 -kit_collaborators kit_id uuid 0 -kit_collaborators user_id uuid 0 -kit_collaborators permission text 0 -kit_collaborators invited_by uuid 0 -kit_collaborators invited_email text 0 -kit_collaborators created_at timestamp with time zone 0 -kit_collaborators updated_at timestamp with time zone 0 -kit_comments id uuid 0 -kit_comments kit_id uuid 0 -kit_comments author_id uuid 0 -kit_comments parent_id uuid 0 -kit_comments item_anchor text 0 -kit_comments body text 0 -kit_comments resolved boolean 0 -kit_comments created_at timestamp with time zone 0 -kit_comments updated_at timestamp with time zone 0 -kit_component_print_areas id uuid 0 -kit_component_print_areas kit_component_id uuid 0 -kit_component_print_areas tabela_preco_id uuid 0 -kit_component_print_areas location_code text 0 -kit_component_print_areas location_name text 0 -kit_component_print_areas location_order integer 0 -kit_component_print_areas max_width numeric 0 -kit_component_print_areas max_height numeric 0 -kit_component_print_areas shape text 0 -kit_component_print_areas is_curved boolean 0 -kit_component_print_areas technique_order integer 0 -kit_component_print_areas is_active boolean 0 -kit_component_print_areas created_at timestamp with time zone 0 -kit_component_print_areas updated_at timestamp with time zone 0 -kit_component_types id uuid 0 -kit_component_types code character varying 0 -kit_component_types name character varying 0 -kit_component_types name_plural character varying 0 -kit_component_types description text 0 -kit_component_types icon character varying 0 -kit_component_types default_material_type_id uuid 0 -kit_component_types secondary_material_type_id uuid 0 -kit_component_types typical_length_mm integer 0 -kit_component_types typical_width_mm integer 0 -kit_component_types typical_height_mm integer 0 -kit_component_types typical_weight_g integer 0 -kit_component_types category character varying 0 -kit_component_types display_order integer 0 -kit_component_types is_active boolean 0 -kit_component_types created_at timestamp with time zone 0 -kit_component_types updated_at timestamp with time zone 0 -kit_component_types is_personalizable_by_default boolean 0 -kit_share_tokens id uuid 0 -kit_share_tokens kit_id uuid 0 -kit_share_tokens seller_id uuid 0 -kit_share_tokens token text 0 -kit_share_tokens client_name text 0 -kit_share_tokens client_email text 0 -kit_share_tokens status text 0 -kit_share_tokens expires_at timestamp with time zone 0 -kit_share_tokens viewed_at timestamp with time zone 0 -kit_share_tokens created_at timestamp with time zone 0 -kit_share_tokens updated_at timestamp with time zone 0 -kit_templates id uuid 0 -kit_templates name text 0 -kit_templates description text 0 -kit_templates category text 0 -kit_templates color text 0 -kit_templates icon text 0 -kit_templates tag text 0 -kit_templates cover_image_url text 0 -kit_templates box_data jsonb 0 -kit_templates items_data jsonb 0 -kit_templates personalization_data jsonb 0 -kit_templates total_price numeric 0 -kit_templates volume_usage_percent numeric 0 -kit_templates usage_count integer 0 -kit_templates is_active boolean 0 -kit_templates created_by uuid 0 -kit_templates created_at timestamp with time zone 0 -kit_templates updated_at timestamp with time zone 0 -kit_variants id uuid 0 -kit_variants kit_master_id uuid 0 -kit_variants label text 0 -kit_variants sort_order integer 0 -kit_variants box_data jsonb 0 -kit_variants items_data jsonb 0 -kit_variants personalization_data jsonb 0 -kit_variants kit_quantity integer 0 -kit_variants total_price numeric 0 -kit_variants created_at timestamp with time zone 0 -kit_variants updated_at timestamp with time zone 0 -login_attempts id uuid 0 -login_attempts email text 0 -login_attempts user_id uuid 0 -login_attempts ip_address text 0 -login_attempts user_agent text 0 -login_attempts success boolean 0 -login_attempts failure_reason text 0 -login_attempts metadata jsonb 0 -login_attempts created_at timestamp with time zone 0 -magic_up_brand_kits id uuid 0 -magic_up_brand_kits user_id uuid 0 -magic_up_brand_kits client_id text 0 -magic_up_brand_kits client_name text 0 -magic_up_brand_kits logo_urls jsonb 0 -magic_up_brand_kits primary_color text 0 -magic_up_brand_kits secondary_color text 0 -magic_up_brand_kits tone_of_voice text 0 -magic_up_brand_kits visual_style text 0 -magic_up_brand_kits required_words ARRAY 0 -magic_up_brand_kits forbidden_words ARRAY 0 -magic_up_brand_kits notes text 0 -magic_up_brand_kits metadata jsonb 0 -magic_up_brand_kits created_at timestamp with time zone 0 -magic_up_brand_kits updated_at timestamp with time zone 0 -magic_up_campaigns id uuid 0 -magic_up_campaigns user_id uuid 0 -magic_up_campaigns client_id text 0 -magic_up_campaigns client_name text 0 -magic_up_campaigns title text 0 -magic_up_campaigns objective text 0 -magic_up_campaigns channel text 0 -magic_up_campaigns audience text 0 -magic_up_campaigns tone text 0 -magic_up_campaigns cta text 0 -magic_up_campaigns occasion text 0 -magic_up_campaigns status text 0 -magic_up_campaigns metadata jsonb 0 -magic_up_campaigns created_at timestamp with time zone 0 -magic_up_campaigns updated_at timestamp with time zone 0 -magic_up_comments id uuid 0 -magic_up_comments user_id uuid 0 -magic_up_comments generation_id uuid 0 -magic_up_comments author_name text 0 -magic_up_comments comment text 0 -magic_up_comments is_public boolean 0 -magic_up_comments created_at timestamp with time zone 0 -magic_up_generations id uuid 0 -magic_up_generations user_id uuid 0 -magic_up_generations product_name text 0 -magic_up_generations scene_title text 0 -magic_up_generations scene_category text 0 -magic_up_generations client_name text 0 -magic_up_generations generated_image_url text 0 -magic_up_generations is_favorite boolean 0 -magic_up_generations created_at timestamp with time zone 0 -magic_up_generations campaign_id uuid 0 -magic_up_generations product_id text 0 -magic_up_generations product_sku text 0 -magic_up_generations prompt_text text 0 -magic_up_generations model text 0 -magic_up_generations channel text 0 -magic_up_generations aspect_ratio text 0 -magic_up_generations quality_score integer 0 -magic_up_generations status text 0 -magic_up_generations tags ARRAY 0 -magic_up_generations metadata jsonb 0 -magic_up_generations copy_pack jsonb 0 -magic_up_generations export_presets jsonb 0 -magic_up_public_shares id uuid 0 -magic_up_public_shares user_id uuid 0 -magic_up_public_shares generation_id uuid 0 -magic_up_public_shares campaign_id uuid 0 -magic_up_public_shares share_token text 0 -magic_up_public_shares expires_at timestamp with time zone 0 -magic_up_public_shares allow_download boolean 0 -magic_up_public_shares allow_comments boolean 0 -magic_up_public_shares status text 0 -magic_up_public_shares metadata jsonb 0 -magic_up_public_shares created_at timestamp with time zone 0 -magic_up_public_shares updated_at timestamp with time zone 0 -magic_up_reactions id uuid 0 -magic_up_reactions user_id uuid 0 -magic_up_reactions generation_id uuid 0 -magic_up_reactions reaction_type text 0 -magic_up_reactions ip_hash text 0 -magic_up_reactions user_agent text 0 -magic_up_reactions created_at timestamp with time zone 0 -markup_configurations id uuid 0 -markup_configurations organization_id uuid 0 -markup_configurations supplier_id uuid 0 -markup_configurations category_id uuid 0 -markup_configurations product_id uuid 0 -markup_configurations variant_id uuid 0 -markup_configurations markup_percent numeric 0 -markup_configurations description text 0 -markup_configurations is_active boolean 0 -markup_configurations priority integer 0 -markup_configurations created_at timestamp with time zone 0 -markup_configurations updated_at timestamp with time zone 0 -markup_configurations created_by uuid 0 -material_equivalences id uuid 0 -material_equivalences supplier_material_id uuid 0 -material_equivalences promo_variation_id uuid 0 -material_equivalences promo_type_id uuid 0 -material_equivalences promo_group_id uuid 0 -material_equivalences match_level character varying 0 -material_equivalences match_quality character varying 0 -material_equivalences confidence_score numeric 0 -material_equivalences notes text 0 -material_equivalences created_at timestamp with time zone 0 -material_equivalences updated_at timestamp with time zone 0 -material_groups id uuid 0 -material_groups organization_id uuid 0 -material_groups name character varying 0 -material_groups slug character varying 0 -material_groups description text 0 -material_groups sort_order integer 0 -material_groups is_active boolean 0 -material_groups created_at timestamp with time zone 0 -material_groups updated_at timestamp with time zone 0 -material_groups created_by uuid 0 -material_groups updated_by uuid 0 -material_types id uuid 0 -material_types organization_id uuid 0 -material_types group_id uuid 0 -material_types name character varying 0 -material_types slug character varying 0 -material_types description text 0 -material_types properties jsonb 0 -material_types display_order integer 0 -material_types is_active boolean 0 -material_types created_at timestamp with time zone 0 -material_types updated_at timestamp with time zone 0 -material_variations id uuid 0 -material_variations organization_id uuid 0 -material_variations type_id uuid 0 -material_variations name character varying 0 -material_variations slug character varying 0 -material_variations description text 0 -material_variations specifications jsonb 0 -material_variations display_order integer 0 -material_variations is_active boolean 0 -material_variations created_at timestamp with time zone 0 -material_variations updated_at timestamp with time zone 0 -mcp_access_violations id uuid 0 -mcp_access_violations user_id uuid 0 -mcp_access_violations reason text 0 -mcp_access_violations source text 0 -mcp_access_violations operation text 0 -mcp_access_violations target_key_id uuid 0 -mcp_access_violations ip_address text 0 -mcp_access_violations user_agent text 0 -mcp_access_violations request_id text 0 -mcp_access_violations details jsonb 0 -mcp_access_violations created_at timestamp with time zone 0 -mcp_api_keys id uuid 0 -mcp_api_keys name text 0 -mcp_api_keys key_hash text 0 -mcp_api_keys key_prefix text 0 -mcp_api_keys scopes ARRAY 0 -mcp_api_keys description text 0 -mcp_api_keys created_by uuid 0 -mcp_api_keys last_used_at timestamp with time zone 0 -mcp_api_keys expires_at timestamp with time zone 0 -mcp_api_keys revoked_at timestamp with time zone 0 -mcp_api_keys created_at timestamp with time zone 0 -mcp_api_keys updated_at timestamp with time zone 0 -mcp_api_keys rotated_from uuid 0 -mcp_full_grantors user_id uuid 0 -mcp_full_grantors granted_by uuid 0 -mcp_full_grantors reason text 0 -mcp_full_grantors granted_at timestamp with time zone 0 -mcp_key_auto_revocations id uuid 0 -mcp_key_auto_revocations key_id uuid 0 -mcp_key_auto_revocations created_by uuid 0 -mcp_key_auto_revocations revoked_at timestamp with time zone 0 -mcp_key_auto_revocations source text 0 -mcp_key_auto_revocations reason text 0 -mcp_key_auto_revocations created_at timestamp with time zone 0 -media_assets id uuid 0 -media_assets product_reference character varying 0 -media_assets variant_sku character varying 0 -media_assets supplier_code character varying 0 -media_assets filename character varying 0 -media_assets original_filename character varying 0 -media_assets source_url text 0 -media_assets cdn_url text 0 -media_assets cloudflare_id character varying 0 -media_assets image_type character varying 0 -media_assets image_role character varying 0 -media_assets width integer 0 -media_assets height integer 0 -media_assets file_size integer 0 -media_assets mime_type character varying 0 -media_assets alt_text text 0 -media_assets status character varying 0 -media_assets error_message text 0 -media_assets retry_count integer 0 -media_assets display_order integer 0 -media_assets is_primary boolean 0 -media_assets is_visible boolean 0 -media_assets created_at timestamp with time zone 0 -media_assets updated_at timestamp with time zone 0 -media_assets processed_at timestamp with time zone 0 -media_sync_log id uuid 0 -media_sync_log sync_type character varying 0 -media_sync_log media_type character varying 0 -media_sync_log image_id uuid 0 -media_sync_log video_id uuid 0 -media_sync_log product_id uuid 0 -media_sync_log source_url text 0 -media_sync_log destination_url text 0 -media_sync_log cloudflare_id character varying 0 -media_sync_log status character varying 0 -media_sync_log error_message text 0 -media_sync_log retry_count integer 0 -media_sync_log file_size_bytes bigint 0 -media_sync_log processing_time_ms integer 0 -media_sync_log synced_by character varying 0 -media_sync_log metadata jsonb 0 -media_sync_log created_at timestamp with time zone 0 -media_sync_queue id uuid 0 -media_sync_queue product_id uuid 0 -media_sync_queue source_url text 0 -media_sync_queue source_type text 0 -media_sync_queue supplier_id uuid 0 -media_sync_queue status text 0 -media_sync_queue priority integer 0 -media_sync_queue attempts integer 0 -media_sync_queue max_attempts integer 0 -media_sync_queue error_message text 0 -media_sync_queue cloudflare_image_id text 0 -media_sync_queue cloudflare_url text 0 -media_sync_queue organization_id uuid 0 -media_sync_queue queued_at timestamp with time zone 0 -media_sync_queue started_at timestamp with time zone 0 -media_sync_queue completed_at timestamp with time zone 0 -mockup_approval_links id uuid 0 -mockup_approval_links job_id uuid 0 -mockup_approval_links client_id uuid 0 -mockup_approval_links public_token text 0 -mockup_approval_links expires_at timestamp with time zone 0 -mockup_approval_links max_uses integer 0 -mockup_approval_links current_uses integer 0 -mockup_approval_links is_active boolean 0 -mockup_approval_links first_accessed_at timestamp with time zone 0 -mockup_approval_links last_accessed_at timestamp with time zone 0 -mockup_approval_links access_count integer 0 -mockup_approval_links approved_at timestamp with time zone 0 -mockup_approval_links rejected_at timestamp with time zone 0 -mockup_approval_links client_notes text 0 -mockup_approval_links created_at timestamp with time zone 0 -mockup_approval_links updated_at timestamp with time zone 0 -mockup_credit_transactions id uuid 0 -mockup_credit_transactions user_id uuid 0 -mockup_credit_transactions credit_account_id uuid 0 -mockup_credit_transactions type text 0 -mockup_credit_transactions amount numeric 0 -mockup_credit_transactions balance_before numeric 0 -mockup_credit_transactions balance_after numeric 0 -mockup_credit_transactions mockup_id uuid 0 -mockup_credit_transactions job_id uuid 0 -mockup_credit_transactions description text 0 -mockup_credit_transactions metadata jsonb 0 -mockup_credit_transactions created_at timestamp with time zone 0 -mockup_credits id uuid 0 -mockup_credits user_id uuid 0 -mockup_credits balance numeric 0 -mockup_credits lifetime_earned numeric 0 -mockup_credits lifetime_spent numeric 0 -mockup_credits monthly_limit numeric 0 -mockup_credits daily_limit numeric 0 -mockup_credits current_month_spent numeric 0 -mockup_credits current_day_spent numeric 0 -mockup_credits last_reset_month date 0 -mockup_credits last_reset_day date 0 -mockup_credits created_at timestamp with time zone 0 -mockup_credits updated_at timestamp with time zone 0 -mockup_drafts id uuid 0 -mockup_drafts user_id uuid 0 -mockup_drafts draft_key text 0 -mockup_drafts product_id text 0 -mockup_drafts product_name text 0 -mockup_drafts technique_id text 0 -mockup_drafts technique_name text 0 -mockup_drafts client_id text 0 -mockup_drafts client_name text 0 -mockup_drafts personalization_areas jsonb 0 -mockup_drafts logo_data text 0 -mockup_drafts created_at timestamp with time zone 0 -mockup_drafts updated_at timestamp with time zone 0 -mockup_generation_jobs id uuid 0 -mockup_generation_jobs user_id uuid 0 -mockup_generation_jobs client_id uuid 0 -mockup_generation_jobs product_id uuid 0 -mockup_generation_jobs product_name text 0 -mockup_generation_jobs product_sku text 0 -mockup_generation_jobs technique_id uuid 0 -mockup_generation_jobs technique_name text 0 -mockup_generation_jobs logo_url text 0 -mockup_generation_jobs logo_filename text 0 -mockup_generation_jobs product_colors jsonb 0 -mockup_generation_jobs colors_count integer 0 -mockup_generation_jobs areas_config jsonb 0 -mockup_generation_jobs art_colors_count integer 0 -mockup_generation_jobs custom_prompt text 0 -mockup_generation_jobs ai_model text 0 -mockup_generation_jobs status text 0 -mockup_generation_jobs total_mockups integer 0 -mockup_generation_jobs completed_mockups integer 0 -mockup_generation_jobs failed_mockups integer 0 -mockup_generation_jobs estimated_cost numeric 0 -mockup_generation_jobs actual_cost numeric 0 -mockup_generation_jobs error_message text 0 -mockup_generation_jobs processing_started_at timestamp with time zone 0 -mockup_generation_jobs processing_completed_at timestamp with time zone 0 -mockup_generation_jobs created_at timestamp with time zone 0 -mockup_generation_jobs updated_at timestamp with time zone 0 -mockup_prompt_configs id uuid 0 -mockup_prompt_configs config_key text 0 -mockup_prompt_configs label text 0 -mockup_prompt_configs prompt_text text 0 -mockup_prompt_configs ai_model text 0 -mockup_prompt_configs technique_id uuid 0 -mockup_prompt_configs is_active boolean 0 -mockup_prompt_configs version integer 0 -mockup_prompt_configs created_at timestamp with time zone 0 -mockup_prompt_configs updated_at timestamp with time zone 0 -mockup_prompt_history id uuid 0 -mockup_prompt_history config_id uuid 0 -mockup_prompt_history config_key text 0 -mockup_prompt_history old_prompt text 0 -mockup_prompt_history new_prompt text 0 -mockup_prompt_history ai_model text 0 -mockup_prompt_history version integer 0 -mockup_prompt_history changed_by uuid 0 -mockup_prompt_history change_notes text 0 -mockup_prompt_history changed_at timestamp with time zone 0 -mockup_templates id uuid 0 -mockup_templates product_id uuid 0 -mockup_templates name text 0 -mockup_templates description text 0 -mockup_templates template_image_url text 0 -mockup_templates predefined_areas jsonb 0 -mockup_templates default_technique_id uuid 0 -mockup_templates available_colors jsonb 0 -mockup_templates usage_count integer 0 -mockup_templates is_featured boolean 0 -mockup_templates is_active boolean 0 -mockup_templates created_at timestamp with time zone 0 -mockup_templates updated_at timestamp with time zone 0 -ncm_codes id uuid 0 -ncm_codes code character varying 0 -ncm_codes code_formatted character varying 0 -ncm_codes chapter character varying 0 -ncm_codes position character varying 0 -ncm_codes subposition character varying 0 -ncm_codes item character varying 0 -ncm_codes description text 0 -ncm_codes chapter_desc text 0 -ncm_codes position_desc text 0 -ncm_codes ipi_rate numeric 0 -ncm_codes import_rate numeric 0 -ncm_codes is_active boolean 0 -ncm_codes source text 0 -ncm_codes notes text 0 -ncm_codes organization_id uuid 0 -ncm_codes created_at timestamp with time zone 0 -ncm_codes updated_at timestamp with time zone 0 -notification_preferences id uuid 0 -notification_preferences user_id uuid 0 -notification_preferences email_notifications boolean 0 -notification_preferences push_notifications boolean 0 -notification_preferences quote_updates boolean 0 -notification_preferences order_updates boolean 0 -notification_preferences system_announcements boolean 0 -notification_preferences marketing boolean 0 -notification_preferences created_at timestamp with time zone 0 -notification_preferences updated_at timestamp with time zone 0 -notification_templates id uuid 0 -notification_templates code text 0 -notification_templates name text 0 -notification_templates subject text 0 -notification_templates body_template text 0 -notification_templates variables jsonb 0 -notification_templates is_active boolean 0 -notification_templates created_at timestamp with time zone 0 -notification_templates updated_at timestamp with time zone 0 -notifications id uuid 0 -notifications user_id uuid 0 -notifications type text 0 -notifications title text 0 -notifications message text 0 -notifications data jsonb 0 -notifications read_at timestamp with time zone 0 -notifications action_url text 0 -notifications is_read boolean 0 -notifications priority text 0 -notifications created_at timestamp with time zone 0 -optimization_queue id uuid 0 -optimization_queue title text 0 -optimization_queue description text 0 -optimization_queue category text 0 -optimization_queue priority integer 0 -optimization_queue status text 0 -optimization_queue result jsonb 0 -optimization_queue error text 0 -optimization_queue guardrail_status text 0 -optimization_queue started_at timestamp with time zone 0 -optimization_queue finished_at timestamp with time zone 0 -optimization_queue created_by uuid 0 -optimization_queue created_at timestamp with time zone 0 -optimization_queue updated_at timestamp with time zone 0 -optimization_queue_runs id uuid 0 -optimization_queue_runs queue_id uuid 0 -optimization_queue_runs status text 0 -optimization_queue_runs notes text 0 -optimization_queue_runs guardrail_status text 0 -optimization_queue_runs duration_ms integer 0 -optimization_queue_runs executed_by uuid 0 -optimization_queue_runs created_at timestamp with time zone 0 -order_item_personalizations id uuid 0 -order_item_personalizations order_item_id uuid 0 -order_item_personalizations technique_id uuid 0 -order_item_personalizations technique_name text 0 -order_item_personalizations location_id uuid 0 -order_item_personalizations location_name text 0 -order_item_personalizations image_url text 0 -order_item_personalizations personalization_text text 0 -order_item_personalizations price_adjustment numeric 0 -order_item_personalizations created_at timestamp with time zone 0 -order_item_personalizations updated_at timestamp with time zone 0 -order_items id uuid 0 -order_items order_id uuid 0 -order_items product_id uuid 0 -order_items quantity integer 0 -order_items unit_price numeric 0 -order_items subtotal numeric 0 -order_items created_at timestamp with time zone 0 -order_items quote_item_id uuid 0 -order_items product_sku text 0 -order_items product_name text 0 -order_items product_description text 0 -order_items product_image_url text 0 -order_items personalization_config jsonb 0 -order_items personalization_cost numeric 0 -order_items discount_amount numeric 0 -order_items production_status text 0 -order_items production_notes text 0 -order_items updated_at timestamp with time zone 0 -orders id uuid 0 -orders order_number text 0 -orders status text 0 -orders total numeric 0 -orders payment_method text 0 -orders payment_status text 0 -orders delivery_address text 0 -orders notes text 0 -orders created_by uuid 0 -orders created_at timestamp with time zone 0 -orders updated_at timestamp with time zone 0 -orders quote_id uuid 0 -orders client_id uuid 0 -orders client_name text 0 -orders assigned_to uuid 0 -orders fulfillment_status text 0 -orders subtotal numeric 0 -orders discount_amount numeric 0 -orders shipping_cost numeric 0 -orders tax_amount numeric 0 -orders paid_amount numeric 0 -orders payment_terms text 0 -orders payment_due_date date 0 -orders shipping_address jsonb 0 -orders shipping_method text 0 -orders estimated_delivery_date date 0 -orders delivered_at timestamp with time zone 0 -orders internal_notes text 0 -orders organization_id uuid 0 -orders seller_id uuid 0 -orders client_company text 0 -orders version integer 0 -orders client_email text 0 -orders client_phone text 0 -orders tracking_number text 0 -orders shipping_type text 0 -orders delivery_time text 0 -organization_members id uuid 0 -organization_members organization_id uuid 0 -organization_members user_id uuid 0 -organization_members role USER-DEFINED 0 -organization_members invited_by uuid 0 -organization_members joined_at timestamp with time zone 0 -organization_members created_at timestamp with time zone 0 -organization_members updated_at timestamp with time zone 0 -organizations id uuid 0 -organizations name text 0 -organizations created_at timestamp with time zone 0 -organizations slug text 0 -organizations logo_url text 0 -organizations description text 0 -organizations is_active boolean 0 -organizations settings jsonb 0 -organizations updated_at timestamp with time zone 0 -outbound_webhooks id uuid 0 -outbound_webhooks name text 0 -outbound_webhooks url text 0 -outbound_webhooks secret_ref text 0 -outbound_webhooks events ARRAY 0 -outbound_webhooks active boolean 0 -outbound_webhooks retry_policy jsonb 0 -outbound_webhooks description text 0 -outbound_webhooks created_by uuid 0 -outbound_webhooks last_triggered_at timestamp with time zone 0 -outbound_webhooks total_success integer 0 -outbound_webhooks total_failure integer 0 -outbound_webhooks created_at timestamp with time zone 0 -outbound_webhooks updated_at timestamp with time zone 0 -outbound_webhooks consecutive_failures integer 0 -outbound_webhooks auto_disabled_at timestamp with time zone 0 -outbound_webhooks auto_disabled_reason text 0 -ownership_audit_reports id uuid 0 -ownership_audit_reports generated_at timestamp with time zone 0 -ownership_audit_reports total_tables_scanned integer 0 -ownership_audit_reports total_issues_found integer 0 -ownership_audit_reports null_owner_count integer 0 -ownership_audit_reports missing_user_count integer 0 -ownership_audit_reports details jsonb 0 -ownership_audit_reports triggered_by text 0 -ownership_audit_reports duration_ms integer 0 -ownership_audit_reports rls_coverage jsonb 0 -ownership_audit_reports rls_gaps_count integer 0 -ownership_repair_logs id uuid 0 -ownership_repair_logs report_id uuid 0 -ownership_repair_logs table_name text 0 -ownership_repair_logs owner_column text 0 -ownership_repair_logs issue_type text 0 -ownership_repair_logs action text 0 -ownership_repair_logs rows_affected integer 0 -ownership_repair_logs dry_run boolean 0 -ownership_repair_logs triggered_by uuid 0 -ownership_repair_logs triggered_by_label text 0 -ownership_repair_logs notes text 0 -ownership_repair_logs error_message text 0 -ownership_repair_logs created_at timestamp with time zone 0 -packaging_compatibility_config id uuid 0 -packaging_compatibility_config config_key character varying 0 -packaging_compatibility_config config_value text 0 -packaging_compatibility_config description text 0 -packaging_compatibility_config created_at timestamp with time zone 0 -packaging_types id uuid 0 -packaging_types code character varying 0 -packaging_types name character varying 0 -packaging_types description text 0 -packaging_types is_gift_box boolean 0 -packaging_types is_protective boolean 0 -packaging_types is_display boolean 0 -packaging_types active boolean 0 -packaging_types created_at timestamp with time zone 0 -packaging_types updated_at timestamp with time zone 0 -packagings id uuid 0 -packagings code character varying 0 -packagings name character varying 0 -packagings description text 0 -packagings packaging_type_id uuid 0 -packagings internal_width_cm numeric 0 -packagings internal_height_cm numeric 0 -packagings internal_length_cm numeric 0 -packagings internal_diameter_cm numeric 0 -packagings external_width_cm numeric 0 -packagings external_height_cm numeric 0 -packagings external_length_cm numeric 0 -packagings external_diameter_cm numeric 0 -packagings weight_g integer 0 -packagings material character varying 0 -packagings color character varying 0 -packagings is_customizable boolean 0 -packagings print_area_width_cm numeric 0 -packagings print_area_height_cm numeric 0 -packagings active boolean 0 -packagings created_at timestamp with time zone 0 -packagings updated_at timestamp with time zone 0 -password_reset_requests id uuid 0 -password_reset_requests email text 0 -password_reset_requests status text 0 -password_reset_requests requested_at timestamp with time zone 0 -password_reset_requests reviewed_at timestamp with time zone 0 -password_reset_requests reviewed_by uuid 0 -password_reset_requests reviewer_notes text 0 -password_reset_requests user_id uuid 0 -permissions code text 0 -permissions category text 0 -permissions label text 0 -permissions description text 0 -permissions created_at timestamp with time zone 0 -personalization_techniques id uuid 0 -personalization_techniques name text 0 -personalization_techniques code text 0 -personalization_techniques description text 0 -personalization_techniques prompt_suffix text 0 -personalization_techniques requires_color_count boolean 0 -personalization_techniques base_cost_multiplier numeric 0 -personalization_techniques is_active boolean 0 -personalization_techniques created_at timestamp with time zone 0 -personalization_techniques updated_at timestamp with time zone 0 -price_history id uuid 0 -price_history variant_id uuid 0 -price_history change_type character varying 0 -price_history old_values jsonb 0 -price_history new_values jsonb 0 -price_history change_reason text 0 -price_history changed_by uuid 0 -price_history changed_at timestamp with time zone 0 -price_history source character varying 0 -print_area_techniques id uuid 0 -print_area_techniques tabela_preco_id uuid 0 -print_area_techniques max_width numeric 0 -print_area_techniques max_height numeric 0 -print_area_techniques is_curved boolean 0 -print_area_techniques shape text 0 -print_area_techniques technique_order integer 0 -print_area_techniques is_active boolean 0 -print_area_techniques created_at timestamp with time zone 0 -print_area_techniques updated_at timestamp with time zone 0 -print_area_techniques product_id uuid 0 -print_area_techniques location_code text 0 -print_area_techniques location_name text 0 -print_area_techniques location_order integer 0 -print_area_techniques notes text 0 -print_area_techniques unit_cost numeric 0 -product_ai_history id uuid 0 -product_ai_history product_id uuid 0 -product_ai_history version integer 0 -product_ai_history ai_title text 0 -product_ai_history ai_description text 0 -product_ai_history model character varying 0 -product_ai_history prompt_tokens integer 0 -product_ai_history completion_tokens integer 0 -product_ai_history total_tokens integer 0 -product_ai_history generation_time_ms integer 0 -product_ai_history context_snapshot jsonb 0 -product_ai_history generated_at timestamp with time zone 0 -product_ai_history created_at timestamp with time zone 0 -product_category_assignments id uuid 0 -product_category_assignments product_id uuid 0 -product_category_assignments category_id uuid 0 -product_category_assignments is_primary boolean 0 -product_category_assignments display_order integer 0 -product_category_assignments created_at timestamp with time zone 0 -product_commemorative_dates id uuid 0 -product_commemorative_dates product_id uuid 0 -product_commemorative_dates commemorative_date_id uuid 0 -product_commemorative_dates source character varying 0 -product_commemorative_dates category_id uuid 0 -product_commemorative_dates is_featured boolean 0 -product_commemorative_dates relevance_score integer 0 -product_commemorative_dates display_order integer 0 -product_commemorative_dates custom_message text 0 -product_commemorative_dates is_active boolean 0 -product_commemorative_dates created_at timestamp with time zone 0 -product_commemorative_dates updated_at timestamp with time zone 0 -product_component_locations id uuid 0 -product_component_locations component_id uuid 0 -product_component_locations location_code text 0 -product_component_locations location_name text 0 -product_component_locations description text 0 -product_component_locations max_width_cm numeric 0 -product_component_locations max_height_cm numeric 0 -product_component_locations is_active boolean 0 -product_component_locations sort_order integer 0 -product_component_locations created_at timestamp with time zone 0 -product_component_locations updated_at timestamp with time zone 0 -product_components id uuid 0 -product_components product_id text 0 -product_components component_code text 0 -product_components component_name text 0 -product_components is_personalizable boolean 0 -product_components is_active boolean 0 -product_components sort_order integer 0 -product_components created_at timestamp with time zone 0 -product_components updated_at timestamp with time zone 0 -product_faqs id uuid 0 -product_faqs product_id uuid 0 -product_faqs category_id uuid 0 -product_faqs faq_scope text 0 -product_faqs question text 0 -product_faqs answer text 0 -product_faqs answer_html text 0 -product_faqs keywords ARRAY 0 -product_faqs display_order integer 0 -product_faqs is_featured boolean 0 -product_faqs is_active boolean 0 -product_faqs view_count integer 0 -product_faqs helpful_count integer 0 -product_faqs not_helpful_count integer 0 -product_faqs created_at timestamp with time zone 0 -product_faqs updated_at timestamp with time zone 0 -product_faqs created_by uuid 0 -product_group_members id uuid 0 -product_group_members product_group_id uuid 0 -product_group_members product_id text 0 -product_group_members use_group_rules boolean 0 -product_group_members created_at timestamp with time zone 0 -product_group_members updated_at timestamp with time zone 0 -product_groups id uuid 0 -product_groups group_code text 0 -product_groups group_name text 0 -product_groups description text 0 -product_groups is_active boolean 0 -product_groups created_at timestamp with time zone 0 -product_groups updated_at timestamp with time zone 0 -product_images id uuid 0 -product_images product_id uuid 0 -product_images variant_id uuid 0 -product_images color_id uuid 0 -product_images cloudflare_image_id character varying 0 -product_images url_cdn text 0 -product_images filename character varying 0 -product_images file_size_bytes bigint 0 -product_images width_px integer 0 -product_images height_px integer 0 -product_images format character varying 0 -product_images image_type character varying 0 -product_images is_primary boolean 0 -product_images display_order integer 0 -product_images applies_to_color boolean 0 -product_images source_supplier character varying 0 -product_images url_original text 0 -product_images is_active boolean 0 -product_images created_at timestamp with time zone 0 -product_images updated_at timestamp with time zone 0 -product_images image_type_id uuid 0 -product_images supplier_code character varying 0 -product_images alt_text text 0 -product_images title_text text 0 -product_images caption text 0 -product_images is_og_image boolean 0 -product_images organization_id uuid 0 -product_included_packagings id uuid 0 -product_included_packagings product_id uuid 0 -product_included_packagings name character varying 0 -product_included_packagings description text 0 -product_included_packagings material character varying 0 -product_included_packagings color character varying 0 -product_included_packagings finish character varying 0 -product_included_packagings has_inner_cradle boolean 0 -product_included_packagings cradle_material character varying 0 -product_included_packagings internal_height_cm numeric 0 -product_included_packagings internal_width_cm numeric 0 -product_included_packagings internal_length_cm numeric 0 -product_included_packagings internal_diameter_cm numeric 0 -product_included_packagings external_height_cm numeric 0 -product_included_packagings external_width_cm numeric 0 -product_included_packagings external_length_cm numeric 0 -product_included_packagings weight_g integer 0 -product_included_packagings is_default boolean 0 -product_included_packagings can_be_disabled boolean 0 -product_included_packagings can_be_customized boolean 0 -product_included_packagings supplier_packaging_code character varying 0 -product_included_packagings supplier_packaging_name character varying 0 -product_included_packagings notes text 0 -product_included_packagings active boolean 0 -product_included_packagings created_at timestamp with time zone 0 -product_included_packagings updated_at timestamp with time zone 0 -product_kit_components id uuid 0 -product_kit_components kit_product_id uuid 0 -product_kit_components component_product_id uuid 0 -product_kit_components quantity integer 0 -product_kit_components is_optional boolean 0 -product_kit_components is_replaceable boolean 0 -product_kit_components allowed_variant_ids jsonb 0 -product_kit_components display_order integer 0 -product_kit_components notes text 0 -product_kit_components created_at timestamp with time zone 0 -product_kit_components updated_at timestamp with time zone 0 -product_kit_components component_code character varying 0 -product_kit_components component_name character varying 0 -product_kit_components component_description text 0 -product_kit_components component_sku character varying 0 -product_kit_components material character varying 0 -product_kit_components color character varying 0 -product_kit_components weight_g integer 0 -product_kit_components length_mm integer 0 -product_kit_components width_mm integer 0 -product_kit_components height_mm integer 0 -product_kit_components images jsonb 0 -product_kit_components primary_image_url text 0 -product_kit_components allows_personalization boolean 0 -product_kit_components personalization_notes text 0 -product_kit_components supplier_component_code character varying 0 -product_kit_components material_type_id uuid 0 -product_kit_components secondary_material_type_id uuid 0 -product_kit_components component_type_code character varying 0 -product_kit_components is_packaging boolean 0 -product_materials id uuid 0 -product_materials organization_id uuid 0 -product_materials product_id uuid 0 -product_materials material_id uuid 0 -product_materials part character varying 0 -product_materials percentage numeric 0 -product_materials notes text 0 -product_materials sort_order integer 0 -product_materials created_at timestamp with time zone 0 -product_materials updated_at timestamp with time zone 0 -product_materials is_active boolean 0 -product_materials created_by uuid 0 -product_materials updated_by uuid 0 -product_packaging_compatibility id uuid 0 -product_packaging_compatibility product_id uuid 0 -product_packaging_compatibility packaging_id uuid 0 -product_packaging_compatibility compatibility_source character varying 0 -product_packaging_compatibility supplier_reference_text text 0 -product_packaging_compatibility supplier_reference_code character varying 0 -product_packaging_compatibility fit_gap_height_mm numeric 0 -product_packaging_compatibility fit_gap_width_mm numeric 0 -product_packaging_compatibility fit_gap_length_mm numeric 0 -product_packaging_compatibility fit_gap_diameter_mm numeric 0 -product_packaging_compatibility fit_gap_min_mm numeric 0 -product_packaging_compatibility fit_gap_avg_mm numeric 0 -product_packaging_compatibility fit_rating character varying 0 -product_packaging_compatibility is_recommended boolean 0 -product_packaging_compatibility is_same_supplier boolean 0 -product_packaging_compatibility display_order integer 0 -product_packaging_compatibility is_verified boolean 0 -product_packaging_compatibility verified_at timestamp with time zone 0 -product_packaging_compatibility verification_notes text 0 -product_packaging_compatibility notes text 0 -product_packaging_compatibility active boolean 0 -product_packaging_compatibility created_at timestamp with time zone 0 -product_packaging_compatibility updated_at timestamp with time zone 0 -product_packagings id uuid 0 -product_packagings product_id uuid 0 -product_packagings packaging_id uuid 0 -product_packagings relationship_type character varying 0 -product_packagings additional_price numeric 0 -product_packagings currency character varying 0 -product_packagings min_quantity integer 0 -product_packagings notes text 0 -product_packagings active boolean 0 -product_packagings created_at timestamp with time zone 0 -product_packagings updated_at timestamp with time zone 0 -product_price_freshness_overrides id uuid 0 -product_price_freshness_overrides product_id text 0 -product_price_freshness_overrides threshold_days integer 0 -product_price_freshness_overrides updated_by uuid 0 -product_price_freshness_overrides created_at timestamp with time zone 0 -product_price_freshness_overrides updated_at timestamp with time zone 0 -product_properties id uuid 0 -product_properties product_id uuid 0 -product_properties property_definition_id uuid 0 -product_properties property_code text 0 -product_properties property_value text 0 -product_properties source text 0 -product_properties raw_value text 0 -product_properties created_at timestamp with time zone 0 -product_properties updated_at timestamp with time zone 0 -product_relationships id uuid 0 -product_relationships product_id uuid 0 -product_relationships related_product_id uuid 0 -product_relationships relationship_type character varying 0 -product_relationships display_order integer 0 -product_relationships is_bidirectional boolean 0 -product_relationships is_active boolean 0 -product_relationships created_at timestamp with time zone 0 -product_search_logs id uuid 0 -product_search_logs query text 0 -product_search_logs filters jsonb 0 -product_search_logs results_count integer 0 -product_search_logs latency_ms integer 0 -product_search_logs created_at timestamp with time zone 0 -product_similarity_group_members id uuid 0 -product_similarity_group_members group_id uuid 0 -product_similarity_group_members product_id uuid 0 -product_similarity_group_members supplier_id uuid 0 -product_similarity_group_members is_reference_product boolean 0 -product_similarity_group_members similarity_score numeric 0 -product_similarity_group_members match_source text 0 -product_similarity_group_members notes text 0 -product_similarity_group_members created_at timestamp with time zone 0 -product_similarity_groups id uuid 0 -product_similarity_groups name text 0 -product_similarity_groups category_id uuid 0 -product_similarity_groups material_family text 0 -product_similarity_groups capacity_band text 0 -product_similarity_groups capacity_min integer 0 -product_similarity_groups capacity_max integer 0 -product_similarity_groups match_criteria jsonb 0 -product_similarity_groups match_source text 0 -product_similarity_groups is_active boolean 0 -product_similarity_groups notes text 0 -product_similarity_groups created_at timestamp with time zone 0 -product_similarity_groups updated_at timestamp with time zone 0 -product_sync_logs id uuid 0 -product_sync_logs source text 0 -product_sync_logs status text 0 -product_sync_logs records_processed integer 0 -product_sync_logs records_inserted integer 0 -product_sync_logs records_updated integer 0 -product_sync_logs records_failed integer 0 -product_sync_logs duration_ms integer 0 -product_sync_logs payload jsonb 0 -product_sync_logs error_message text 0 -product_sync_logs triggered_by uuid 0 -product_sync_logs created_at timestamp with time zone 0 -product_sync_logs products_received integer 0 -product_sync_logs products_created integer 0 -product_sync_logs products_updated integer 0 -product_sync_logs products_failed integer 0 -product_sync_logs completed_at timestamp with time zone 0 -product_tags id uuid 0 -product_tags product_id uuid 0 -product_tags tag_id uuid 0 -product_tags created_at timestamp with time zone 0 -product_target_audiences id uuid 0 -product_target_audiences product_id uuid 0 -product_target_audiences target_audience_id uuid 0 -product_target_audiences source character varying 0 -product_target_audiences category_id uuid 0 -product_target_audiences is_featured boolean 0 -product_target_audiences relevance_score integer 0 -product_target_audiences is_active boolean 0 -product_target_audiences created_at timestamp with time zone 0 -product_target_audiences updated_at timestamp with time zone 0 -product_variants id uuid 0 -product_variants product_id uuid 0 -product_variants sku text 0 -product_variants name text 0 -product_variants attributes jsonb 0 -product_variants stock_quantity integer 0 -product_variants images jsonb 0 -product_variants is_active boolean 0 -product_variants created_at timestamp with time zone 0 -product_variants updated_at timestamp with time zone 0 -product_variants sku_promo character varying 0 -product_variants color_id uuid 0 -product_variants size_id uuid 0 -product_variants supplier_sku character varying 0 -product_variants color_code character varying 0 -product_variants color_name character varying 0 -product_variants color_hex character varying 0 -product_variants size_code character varying 0 -product_variants capacity_ml integer 0 -product_variants last_sync_at timestamp with time zone 0 -product_variants last_sync_supplier_id uuid 0 -product_variants selected_thumbnail character varying 0 -product_variants bitrix_product_id integer 0 -product_variants CodigoXbz text 0 -product_videos id uuid 0 -product_videos product_id uuid 0 -product_videos cloudflare_video_id character varying 0 -product_videos url_stream text 0 -product_videos url_hls text 0 -product_videos url_dash text 0 -product_videos url_thumbnail text 0 -product_videos filename character varying 0 -product_videos file_size_bytes bigint 0 -product_videos duration_seconds integer 0 -product_videos width_px integer 0 -product_videos height_px integer 0 -product_videos video_type character varying 0 -product_videos title character varying 0 -product_videos description text 0 -product_videos is_primary boolean 0 -product_videos display_order integer 0 -product_videos source_supplier character varying 0 -product_videos source_youtube_id character varying 0 -product_videos url_original text 0 -product_videos is_active boolean 0 -product_videos created_at timestamp with time zone 0 -product_videos updated_at timestamp with time zone 0 -product_videos video_type_id uuid 0 -product_videos cloudflare_status character varying 0 -product_videos cloudflare_error text 0 -product_videos uploaded_at timestamp with time zone 0 -product_videos organization_id uuid 0 -product_views id uuid 0 -product_views product_id text 0 -product_views product_sku text 0 -product_views product_name text 0 -product_views seller_id uuid 0 -product_views view_type text 0 -product_views created_at timestamp with time zone 0 -products id uuid 0 -products name text 0 -products description text 0 -products sku text 0 -products category_id uuid 0 -products supplier_id uuid 0 -products cost_price numeric 0 -products sale_price numeric 0 -products stock_quantity integer 0 -products active boolean 0 -products created_at timestamp with time zone 0 -products updated_at timestamp with time zone 0 -products suggested_price numeric 0 -products dimensions jsonb 0 -products images jsonb 0 -products primary_image_url text 0 -products videos jsonb 0 -products allows_personalization boolean 0 -products colors jsonb 0 -products materials jsonb 0 -products tags jsonb 0 -products meta_title text 0 -products meta_description text 0 -products meta_keywords ARRAY 0 -products is_featured boolean 0 -products is_new boolean 0 -products is_on_sale boolean 0 -products view_count integer 0 -products favorite_count integer 0 -products order_count integer 0 -products organization_id uuid 0 -products product_type text 0 -products is_active boolean 0 -products created_by uuid 0 -products updated_by uuid 0 -products sku_promo character varying 0 -products short_description character varying 0 -products main_category_id uuid 0 -products brand character varying 0 -products is_deleted boolean 0 -products deleted_at timestamp with time zone 0 -products is_kit boolean 0 -products is_bestseller boolean 0 -products min_quantity integer 0 -products box_length_mm integer 0 -products box_width_mm integer 0 -products box_height_mm integer 0 -products box_weight_kg numeric 0 -products has_colors boolean 0 -products has_sizes boolean 0 -products ean character varying 0 -products gtin character varying 0 -products ncm_code character varying 0 -products origin_country character varying 0 -products warranty_months integer 0 -products manufacturer_sku character varying 0 -products last_stock_update_at timestamp with time zone 0 -products supplier_reference character varying 0 -products is_textil boolean 0 -products has_capacity boolean 0 -products combined_sizes character varying 0 -products gender character varying 0 -products is_stockout boolean 0 -products is_online_exclusive boolean 0 -products catalog_page integer 0 -products weight_g integer 0 -products length_cm numeric 0 -products width_cm numeric 0 -products height_cm numeric 0 -products dimensions_display character varying 0 -products box_length_cm numeric 0 -products box_width_cm numeric 0 -products box_height_cm numeric 0 -products box_volume_cm3 numeric 0 -products box_quantity integer 0 -products box_inner_quantity integer 0 -products packing_type character varying 0 -products repacking_type character varying 0 -products capacities character varying 0 -products last_sync_at timestamp with time zone 0 -products last_sync_supplier_id uuid 0 -products sync_status character varying 0 -products diameter_cm numeric 0 -products shape_type character varying 0 -products internal_height_cm numeric 0 -products internal_width_cm numeric 0 -products internal_length_cm numeric 0 -products internal_diameter_cm numeric 0 -products packaging_material character varying 0 -products packaging_color character varying 0 -products has_inner_cradle boolean 0 -products cradle_material character varying 0 -products packaging_finish character varying 0 -products is_imported boolean 0 -products lead_time_days integer 0 -products requires_minimum_order boolean 0 -products supply_mode character varying 0 -products is_thermal boolean 0 -products capacity_ml integer 0 -products slug text 0 -products ai_summary text 0 -products key_benefits ARRAY 0 -products use_cases ARRAY 0 -products target_audience ARRAY 0 -products schema_json jsonb 0 -products canonical_url text 0 -products robots_meta text 0 -products seo_score integer 0 -products seo_last_audit_at timestamp with time zone 0 -products seo_issues jsonb 0 -products og_title text 0 -products og_description text 0 -products og_image_url text 0 -products description_packaging_info jsonb 0 -products has_optional_packaging boolean 0 -products optional_packaging_ref character varying 0 -products packing_classification character varying 0 -products ipi_rate numeric 0 -products tax_reference_state character varying 0 -products engraving_type text 0 -products supplier_updated_at timestamp with time zone 0 -products has_gift_box boolean 0 -products min_order_quantity integer 0 -products ai_title text 0 -products ai_description text 0 -products ai_version integer 0 -products ai_generated_at timestamp with time zone 0 -products ai_model character varying 0 -products box_image text 0 -products repacking_classification text 0 -products has_commercial_packaging boolean 0 -products packaging_context text 0 -products bitrix_product_id integer 0 -products novelty_detected_at timestamp with time zone 0 -products novelty_expires_at timestamp with time zone 0 -products ncm_id uuid 0 -products bitrix_images_synced_at timestamp with time zone 0 -products is_featured_expires_at timestamp with time zone 0 -products is_bestseller_expires_at timestamp with time zone 0 -products is_on_sale_expires_at timestamp with time zone 0 -products is_new_expires_at timestamp with time zone 0 -products supplier_product_url text 0 -products freight_class character varying 0 -products cubic_weight numeric 0 -products auto_category text 0 -products auto_material text 0 -products classification_confidence double precision 0 -products price_updated_at timestamp with time zone 0 -produto_ramo_atividade id uuid 0 -produto_ramo_atividade produto_id uuid 0 -produto_ramo_atividade ramo_atividade_filho_id uuid 0 -produto_ramo_atividade created_at timestamp with time zone 0 -profiles id uuid 0 -profiles email text 0 -profiles full_name text 0 -profiles role text 0 -profiles created_at timestamp with time zone 0 -profiles updated_at timestamp with time zone 0 -profiles avatar_url text 0 -profiles phone text 0 -profiles department text 0 -profiles is_active boolean 0 -profiles last_login_at timestamp with time zone 0 -profiles preferences jsonb 0 -profiles bitrix_id integer 0 -profiles user_id uuid 0 -property_definitions id uuid 0 -property_definitions code text 0 -property_definitions name_pt text 0 -property_definitions name_en text 0 -property_definitions category text 0 -property_definitions icon_code text 0 -property_definitions is_thermal_indicator boolean 0 -property_definitions is_premium_indicator boolean 0 -property_definitions display_order integer 0 -property_definitions active boolean 0 -property_definitions created_at timestamp with time zone 0 -property_definitions updated_at timestamp with time zone 0 -public_token_failures id uuid 0 -public_token_failures resource_type text 0 -public_token_failures resource_id text 0 -public_token_failures attempted_token text 0 -public_token_failures ip_address text 0 -public_token_failures user_agent text 0 -public_token_failures reason text 0 -public_token_failures created_at timestamp with time zone 0 -push_subscriptions id uuid 0 -push_subscriptions user_id uuid 0 -push_subscriptions endpoint text 0 -push_subscriptions keys jsonb 0 -push_subscriptions is_active boolean 0 -push_subscriptions created_at timestamp with time zone 0 -push_subscriptions updated_at timestamp with time zone 0 -query_telemetry id uuid 0 -query_telemetry operation text 0 -query_telemetry table_name text 0 -query_telemetry rpc_name text 0 -query_telemetry duration_ms integer 0 -query_telemetry record_count integer 0 -query_telemetry query_limit integer 0 -query_telemetry query_offset integer 0 -query_telemetry count_mode text 0 -query_telemetry severity text 0 -query_telemetry error_message text 0 -query_telemetry user_id uuid 0 -query_telemetry created_at timestamp with time zone 0 -query_telemetry error_kind text 0 -query_telemetry retry_count integer 0 -query_telemetry cache_hit boolean 0 -query_telemetry is_cold_start boolean 0 -query_telemetry is_503 boolean 0 -quote_approval_tokens id uuid 0 -quote_approval_tokens quote_id text 0 -quote_approval_tokens token text 0 -quote_approval_tokens seller_id uuid 0 -quote_approval_tokens client_name text 0 -quote_approval_tokens client_email text 0 -quote_approval_tokens status text 0 -quote_approval_tokens expires_at timestamp with time zone 0 -quote_approval_tokens viewed_at timestamp with time zone 0 -quote_approval_tokens responded_at timestamp with time zone 0 -quote_approval_tokens response text 0 -quote_approval_tokens response_notes text 0 -quote_approval_tokens created_at timestamp with time zone 0 -quote_approval_tokens updated_at timestamp with time zone 0 -quote_approval_tokens signer_name text 0 -quote_approval_tokens signer_document text 0 -quote_approval_tokens signer_ip text 0 -quote_approval_tokens signer_user_agent text 0 -quote_approval_tokens signature_hash text 0 -quote_approval_tokens signed_at timestamp with time zone 0 -quote_comments id uuid 0 -quote_comments quote_id uuid 0 -quote_comments user_id uuid 0 -quote_comments comment text 0 -quote_comments is_internal boolean 0 -quote_comments created_at timestamp with time zone 0 -quote_comments updated_at timestamp with time zone 0 -quote_drafts id uuid 0 -quote_drafts user_id uuid 0 -quote_drafts data jsonb 0 -quote_drafts last_saved_at timestamp with time zone 0 -quote_history id uuid 0 -quote_history quote_id uuid 0 -quote_history user_id uuid 0 -quote_history action text 0 -quote_history field_changed text 0 -quote_history old_value text 0 -quote_history new_value text 0 -quote_history description text 0 -quote_history metadata jsonb 0 -quote_history created_at timestamp with time zone 0 -quote_item_personalizations id uuid 0 -quote_item_personalizations quote_item_id uuid 0 -quote_item_personalizations technique_id uuid 0 -quote_item_personalizations technique_name text 0 -quote_item_personalizations colors_count integer 0 -quote_item_personalizations positions_count integer 0 -quote_item_personalizations area_cm2 numeric 0 -quote_item_personalizations width_cm numeric 0 -quote_item_personalizations height_cm numeric 0 -quote_item_personalizations personalized_quantity integer 0 -quote_item_personalizations setup_cost numeric 0 -quote_item_personalizations unit_cost numeric 0 -quote_item_personalizations total_cost numeric 0 -quote_item_personalizations notes text 0 -quote_item_personalizations created_at timestamp with time zone 0 -quote_item_personalizations updated_at timestamp with time zone 0 -quote_item_personalizations location_code text 0 -quote_item_personalizations location_name text 0 -quote_items id uuid 0 -quote_items quote_id uuid 0 -quote_items product_id uuid 0 -quote_items product_sku text 0 -quote_items product_name text 0 -quote_items product_description text 0 -quote_items product_image_url text 0 -quote_items has_personalization boolean 0 -quote_items personalization_config jsonb 0 -quote_items personalization_cost numeric 0 -quote_items quantity integer 0 -quote_items unit_price numeric 0 -quote_items discount_percentage numeric 0 -quote_items discount_amount numeric 0 -quote_items subtotal numeric 0 -quote_items mockup_urls jsonb 0 -quote_items artwork_urls jsonb 0 -quote_items notes text 0 -quote_items created_at timestamp with time zone 0 -quote_items updated_at timestamp with time zone 0 -quote_items color_name text 0 -quote_items color_hex text 0 -quote_items bitrix_product_id text 0 -quote_items kit_group_id uuid 0 -quote_items kit_name text 0 -quote_items size_code text 0 -quote_items gender text 0 -quote_items sort_order integer 0 -quote_items price_updated_at timestamp with time zone 0 -quote_items price_freshness_threshold_days integer 0 -quote_items price_confirmed_at timestamp with time zone 0 -quote_templates id uuid 0 -quote_templates name text 0 -quote_templates description text 0 -quote_templates category text 0 -quote_templates items jsonb 0 -quote_templates payment_terms text 0 -quote_templates delivery_time text 0 -quote_templates notes text 0 -quote_templates is_active boolean 0 -quote_templates usage_count integer 0 -quote_templates created_by uuid 0 -quote_templates created_at timestamp with time zone 0 -quote_templates updated_at timestamp with time zone 0 -quote_templates seller_id uuid 0 -quote_templates template_data jsonb 0 -quote_templates discount_percent numeric 0 -quote_templates discount_amount numeric 0 -quote_templates internal_notes text 0 -quote_templates is_default boolean 0 -quote_templates validity_days integer 0 -quote_versions id uuid 0 -quote_versions quote_id uuid 0 -quote_versions version_number integer 0 -quote_versions snapshot jsonb 0 -quote_versions change_summary text 0 -quote_versions created_by uuid 0 -quote_versions created_at timestamp with time zone 0 -quotes id uuid 0 -quotes quote_number text 0 -quotes client_id uuid 0 -quotes client_name text 0 -quotes created_by uuid 0 -quotes assigned_to uuid 0 -quotes status text 0 -quotes stage text 0 -quotes priority text 0 -quotes subtotal numeric 0 -quotes discount_amount numeric 0 -quotes shipping_cost numeric 0 -quotes tax_amount numeric 0 -quotes total numeric 0 -quotes valid_until date 0 -quotes estimated_delivery_days integer 0 -quotes notes text 0 -quotes internal_notes text 0 -quotes payment_terms text 0 -quotes tags jsonb 0 -quotes approval_token text 0 -quotes approved_at timestamp with time zone 0 -quotes approved_by_client_name text 0 -quotes client_feedback text 0 -quotes viewed_at timestamp with time zone 0 -quotes view_count integer 0 -quotes last_sent_at timestamp with time zone 0 -quotes converted_to_order_id uuid 0 -quotes converted_at timestamp with time zone 0 -quotes conversion_notes text 0 -quotes created_at timestamp with time zone 0 -quotes updated_at timestamp with time zone 0 -quotes organization_id uuid 0 -quotes seller_id uuid 0 -quotes discount_percent numeric 0 -quotes negotiation_markup_percent numeric 0 -quotes real_subtotal numeric 0 -quotes real_discount_percent numeric 0 -quotes version integer 0 -quotes client_email text 0 -quotes client_phone text 0 -quotes client_company text 0 -quotes client_cnpj text 0 -quotes delivery_time text 0 -quotes shipping_type text 0 -quotes bitrix_deal_id text 0 -quotes bitrix_quote_id text 0 -quotes synced_to_bitrix boolean 0 -quotes synced_at timestamp with time zone 0 -quotes client_response text 0 -quotes client_response_at timestamp with time zone 0 -quotes client_response_notes text 0 -quotes shipping_method text 0 -quotes parent_quote_id uuid 0 -quotes is_latest_version boolean 0 -quotes payment_method text 0 -quotes sent_at timestamp with time zone 0 -ramo_atividade id uuid 0 -ramo_atividade nome character varying 0 -ramo_atividade slug character varying 0 -ramo_atividade descricao text 0 -ramo_atividade icone character varying 0 -ramo_atividade cor character varying 0 -ramo_atividade ativo boolean 0 -ramo_atividade ordem integer 0 -ramo_atividade created_at timestamp with time zone 0 -ramo_atividade updated_at timestamp with time zone 0 -ramo_atividade_filho id uuid 0 -ramo_atividade_filho ramo_atividade_id uuid 0 -ramo_atividade_filho nome character varying 0 -ramo_atividade_filho slug character varying 0 -ramo_atividade_filho descricao text 0 -ramo_atividade_filho icone character varying 0 -ramo_atividade_filho ativo boolean 0 -ramo_atividade_filho ordem integer 0 -ramo_atividade_filho created_at timestamp with time zone 0 -ramo_atividade_filho updated_at timestamp with time zone 0 -recently_viewed_products id uuid 0 -recently_viewed_products user_id uuid 0 -recently_viewed_products product_id text 0 -recently_viewed_products viewed_at timestamp with time zone 0 -request_rate_limits id uuid 0 -request_rate_limits identifier text 0 -request_rate_limits endpoint text 0 -request_rate_limits request_count integer 0 -request_rate_limits window_start timestamp with time zone 0 -request_rate_limits blocked_until timestamp with time zone 0 -request_rate_limits created_at timestamp with time zone 0 -request_rate_limits updated_at timestamp with time zone 0 -rls_denial_log id uuid 0 -rls_denial_log user_id uuid 0 -rls_denial_log user_email text 0 -rls_denial_log user_role text 0 -rls_denial_log table_name text 0 -rls_denial_log operation text 0 -rls_denial_log endpoint text 0 -rls_denial_log query_summary text 0 -rls_denial_log target_id uuid 0 -rls_denial_log target_seller_id uuid 0 -rls_denial_log policy_hint text 0 -rls_denial_log error_code text 0 -rls_denial_log error_message text 0 -rls_denial_log user_agent text 0 -rls_denial_log ip_address inet 0 -rls_denial_log created_at timestamp with time zone 0 -role_migration_batches id uuid 0 -role_migration_batches label text 0 -role_migration_batches reason text 0 -role_migration_batches initiated_by uuid 0 -role_migration_batches dry_run boolean 0 -role_migration_batches status USER-DEFINED 0 -role_migration_batches total_items integer 0 -role_migration_batches success_count integer 0 -role_migration_batches failed_count integer 0 -role_migration_batches skipped_count integer 0 -role_migration_batches started_at timestamp with time zone 0 -role_migration_batches finished_at timestamp with time zone 0 -role_migration_batches duration_ms integer 0 -role_migration_batches created_at timestamp with time zone 0 -role_migration_items id uuid 0 -role_migration_items batch_id uuid 0 -role_migration_items user_id uuid 0 -role_migration_items user_email text 0 -role_migration_items from_role USER-DEFINED 0 -role_migration_items to_role USER-DEFINED 0 -role_migration_items operation text 0 -role_migration_items status USER-DEFINED 0 -role_migration_items error_message text 0 -role_migration_items duration_ms integer 0 -role_migration_items processed_at timestamp with time zone 0 -role_migration_items created_at timestamp with time zone 0 -role_permissions id uuid 0 -role_permissions role USER-DEFINED 0 -role_permissions permission_code text 0 -role_permissions created_at timestamp with time zone 0 -saved_filters id uuid 0 -saved_filters user_id uuid 0 -saved_filters name text 0 -saved_filters filter_config jsonb 0 -saved_filters is_default boolean 0 -saved_filters category text 0 -saved_filters created_at timestamp with time zone 0 -saved_filters updated_at timestamp with time zone 0 -saved_filters description text 0 -saved_filters icon text 0 -saved_filters color text 0 -saved_trends_views id uuid 0 -saved_trends_views user_id uuid 0 -saved_trends_views name text 0 -saved_trends_views filters jsonb 0 -saved_trends_views created_at timestamp with time zone 0 -saved_trends_views updated_at timestamp with time zone 0 -scheduled_reports id uuid 0 -scheduled_reports user_id uuid 0 -scheduled_reports report_type text 0 -scheduled_reports frequency text 0 -scheduled_reports email_to text 0 -scheduled_reports report_name text 0 -scheduled_reports filters jsonb 0 -scheduled_reports is_active boolean 0 -scheduled_reports last_sent_at timestamp with time zone 0 -scheduled_reports next_run_at timestamp with time zone 0 -scheduled_reports created_at timestamp with time zone 0 -scheduled_reports updated_at timestamp with time zone 0 -schema_drift_allowlist table_name text 0 -schema_drift_allowlist reason text 0 -schema_drift_allowlist added_by text 0 -schema_drift_allowlist added_at timestamp with time zone 0 -schema_drift_allowlist metadata jsonb 0 -schema_drift_log id uuid 0 -schema_drift_log ran_at timestamp with time zone 0 -schema_drift_log has_drift boolean 0 -schema_drift_log tables_oficial integer 0 -schema_drift_log tables_lovable integer 0 -schema_drift_log only_oficial ARRAY 0 -schema_drift_log only_lovable ARRAY 0 -schema_drift_log schema_diff jsonb 0 -schema_drift_log notification_sent boolean 0 -schema_drift_log error_message text 0 -scraper_checkpoints id uuid 0 -scraper_checkpoints checkpoint_name text 0 -scraper_checkpoints phase text 0 -scraper_checkpoints status text 0 -scraper_checkpoints total_records integer 0 -scraper_checkpoints processed_records integer 0 -scraper_checkpoints error_records integer 0 -scraper_checkpoints metrics jsonb 0 -scraper_checkpoints started_at timestamp with time zone 0 -scraper_checkpoints completed_at timestamp with time zone 0 -scraper_checkpoints notes text 0 -scraper_checkpoints created_at timestamp with time zone 0 -scraper_images_staging id uuid 0 -scraper_images_staging product_id uuid 0 -scraper_images_staging sku text 0 -scraper_images_staging supplier_id uuid 0 -scraper_images_staging woo_image_id integer 0 -scraper_images_staging woo_product_id integer 0 -scraper_images_staging image_name text 0 -scraper_images_staging image_src text 0 -scraper_images_staging image_position integer 0 -scraper_images_staging image_alt text 0 -scraper_images_staging image_type_detected text 0 -scraper_images_staging color_code_detected text 0 -scraper_images_staging color_id uuid 0 -scraper_images_staging exists_in_db boolean 0 -scraper_images_staging is_duplicate boolean 0 -scraper_images_staging sha256_hash text 0 -scraper_images_staging status text 0 -scraper_images_staging error_message text 0 -scraper_images_staging retry_count integer 0 -scraper_images_staging cloudflare_image_id text 0 -scraper_images_staging url_cdn text 0 -scraper_images_staging batch_id text 0 -scraper_images_staging checkpoint text 0 -scraper_images_staging created_at timestamp with time zone 0 -scraper_images_staging updated_at timestamp with time zone 0 -scraper_images_staging processed_at timestamp with time zone 0 -search_analytics id uuid 0 -search_analytics user_id uuid 0 -search_analytics search_term text 0 -search_analytics results_count integer 0 -search_analytics search_context text 0 -search_analytics created_at timestamp with time zone 0 -search_queries id uuid 0 -search_queries user_id uuid 0 -search_queries query text 0 -search_queries results_count integer 0 -search_queries clicked_product_id uuid 0 -search_queries filters_applied jsonb 0 -search_queries created_at timestamp with time zone 0 -secret_rotation_log id uuid 0 -secret_rotation_log secret_name text 0 -secret_rotation_log rotated_by uuid 0 -secret_rotation_log previous_suffix text 0 -secret_rotation_log new_suffix text 0 -secret_rotation_log notes text 0 -secret_rotation_log action_type text 0 -secret_rotation_log rotated_at timestamp with time zone 0 -security_settings setting_key text 0 -security_settings setting_value jsonb 0 -security_settings description text 0 -security_settings updated_by uuid 0 -security_settings created_at timestamp with time zone 0 -security_settings updated_at timestamp with time zone 0 -seller_cart_items id uuid 0 -seller_cart_items cart_id uuid 0 -seller_cart_items product_id text 0 -seller_cart_items product_name text 0 -seller_cart_items product_sku text 0 -seller_cart_items product_image_url text 0 -seller_cart_items product_price numeric 0 -seller_cart_items quantity integer 0 -seller_cart_items color_name text 0 -seller_cart_items color_hex text 0 -seller_cart_items notes text 0 -seller_cart_items sort_order integer 0 -seller_cart_items created_at timestamp with time zone 0 -seller_cart_items updated_at timestamp with time zone 0 -seller_carts id uuid 0 -seller_carts seller_id uuid 0 -seller_carts company_id text 0 -seller_carts company_name text 0 -seller_carts company_location text 0 -seller_carts company_logo_url text 0 -seller_carts notes text 0 -seller_carts status text 0 -seller_carts created_at timestamp with time zone 0 -seller_carts updated_at timestamp with time zone 0 -seller_discount_limits id uuid 0 -seller_discount_limits user_id uuid 0 -seller_discount_limits max_discount_percent numeric 0 -seller_discount_limits approval_required_above numeric 0 -seller_discount_limits notes text 0 -seller_discount_limits set_by uuid 0 -seller_discount_limits created_at timestamp with time zone 0 -seller_discount_limits updated_at timestamp with time zone 0 -seo_audit_log id uuid 0 -seo_audit_log entity_type text 0 -seo_audit_log entity_id uuid 0 -seo_audit_log seo_score integer 0 -seo_audit_log previous_score integer 0 -seo_audit_log issues jsonb 0 -seo_audit_log warnings jsonb 0 -seo_audit_log passed_checks jsonb 0 -seo_audit_log suggestions jsonb 0 -seo_audit_log meta_snapshot jsonb 0 -seo_audit_log audited_at timestamp with time zone 0 -seo_audit_log audit_trigger text 0 -seo_redirects id uuid 0 -seo_redirects source_path text 0 -seo_redirects target_path text 0 -seo_redirects redirect_type integer 0 -seo_redirects is_regex boolean 0 -seo_redirects is_active boolean 0 -seo_redirects reason text 0 -seo_redirects hit_count integer 0 -seo_redirects last_hit_at timestamp with time zone 0 -seo_redirects created_at timestamp with time zone 0 -seo_redirects expires_at timestamp with time zone 0 -seo_redirects created_by uuid 0 -simulator_wizard_drafts id uuid 0 -simulator_wizard_drafts user_id uuid 0 -simulator_wizard_drafts title text 0 -simulator_wizard_drafts product_data jsonb 0 -simulator_wizard_drafts quantity integer 0 -simulator_wizard_drafts personalizations jsonb 0 -simulator_wizard_drafts wizard_step text 0 -simulator_wizard_drafts created_at timestamp with time zone 0 -simulator_wizard_drafts updated_at timestamp with time zone 0 -sm_images_staging id uuid 0 -sm_images_staging sku text 0 -sm_images_staging product_id uuid 0 -sm_images_staging variant_id uuid 0 -sm_images_staging supplier_id uuid 0 -sm_images_staging source_url text 0 -sm_images_staging image_slug text 0 -sm_images_staging image_id_site integer 0 -sm_images_staging image_type text 0 -sm_images_staging is_primary boolean 0 -sm_images_staging display_order integer 0 -sm_images_staging source text 0 -sm_images_staging status text 0 -sm_images_staging cloudflare_image_id text 0 -sm_images_staging cloudflare_url text 0 -sm_images_staging uploaded_at timestamp with time zone 0 -sm_images_staging product_image_id uuid 0 -sm_images_staging populated_at timestamp with time zone 0 -sm_images_staging partition_id integer 0 -sm_images_staging error_message text 0 -sm_images_staging retry_count integer 0 -sm_images_staging created_at timestamp with time zone 0 -sm_images_staging updated_at timestamp with time zone 0 -sm_worker_partitions id integer 0 -sm_worker_partitions worker_id integer 0 -sm_worker_partitions total_skus integer 0 -sm_worker_partitions total_images integer 0 -sm_worker_partitions total_uploaded integer 0 -sm_worker_partitions total_errors integer 0 -sm_worker_partitions status text 0 -sm_worker_partitions started_at timestamp with time zone 0 -sm_worker_partitions completed_at timestamp with time zone 0 -sm_worker_partitions last_invocation_at timestamp with time zone 0 -sm_worker_partitions invocation_count integer 0 -sm_worker_partitions created_at timestamp with time zone 0 -step_up_audit_log id uuid 0 -step_up_audit_log user_id uuid 0 -step_up_audit_log action USER-DEFINED 0 -step_up_audit_log target_ref text 0 -step_up_audit_log event_type text 0 -step_up_audit_log challenge_id uuid 0 -step_up_audit_log token_id uuid 0 -step_up_audit_log ip_address inet 0 -step_up_audit_log user_agent text 0 -step_up_audit_log metadata jsonb 0 -step_up_audit_log created_at timestamp with time zone 0 -step_up_challenges id uuid 0 -step_up_challenges user_id uuid 0 -step_up_challenges action USER-DEFINED 0 -step_up_challenges target_ref text 0 -step_up_challenges otp_hash text 0 -step_up_challenges attempts smallint 0 -step_up_challenges max_attempts smallint 0 -step_up_challenges password_verified boolean 0 -step_up_challenges otp_verified boolean 0 -step_up_challenges consumed boolean 0 -step_up_challenges created_at timestamp with time zone 0 -step_up_challenges expires_at timestamp with time zone 0 -step_up_challenges ip_address inet 0 -step_up_challenges user_agent text 0 -step_up_tokens id uuid 0 -step_up_tokens user_id uuid 0 -step_up_tokens action USER-DEFINED 0 -step_up_tokens target_ref text 0 -step_up_tokens token_hash text 0 -step_up_tokens challenge_id uuid 0 -step_up_tokens consumed boolean 0 -step_up_tokens created_at timestamp with time zone 0 -step_up_tokens expires_at timestamp with time zone 0 -step_up_tokens consumed_at timestamp with time zone 0 -stock_daily_summary id bigint 0 -stock_daily_summary variant_supplier_source_id uuid 0 -stock_daily_summary supplier_id uuid 0 -stock_daily_summary supplier_branch_id uuid 0 -stock_daily_summary variant_id uuid 0 -stock_daily_summary product_id uuid 0 -stock_daily_summary summary_date date 0 -stock_daily_summary stock_open integer 0 -stock_daily_summary stock_close integer 0 -stock_daily_summary stock_min integer 0 -stock_daily_summary stock_max integer 0 -stock_daily_summary net_change integer 0 -stock_daily_summary units_depleted integer 0 -stock_daily_summary units_restocked integer 0 -stock_daily_summary restock_detected boolean 0 -stock_daily_summary restock_quantity integer 0 -stock_daily_summary restock_count smallint 0 -stock_daily_summary cost_price_open numeric 0 -stock_daily_summary cost_price_close numeric 0 -stock_daily_summary price_changed boolean 0 -stock_daily_summary sync_count smallint 0 -stock_daily_summary created_at timestamp with time zone 0 -stock_snapshots id bigint 0 -stock_snapshots variant_supplier_source_id uuid 0 -stock_snapshots supplier_id uuid 0 -stock_snapshots supplier_branch_id uuid 0 -stock_snapshots variant_id uuid 0 -stock_snapshots product_id uuid 0 -stock_snapshots stock_main_old integer 0 -stock_snapshots stock_main_new integer 0 -stock_snapshots stock_main_delta integer 0 -stock_snapshots stock_other_old integer 0 -stock_snapshots stock_other_new integer 0 -stock_snapshots stock_other_delta integer 0 -stock_snapshots stock_total_old integer 0 -stock_snapshots stock_total_new integer 0 -stock_snapshots cost_price_old numeric 0 -stock_snapshots cost_price_new numeric 0 -stock_snapshots cost_price_delta numeric 0 -stock_snapshots change_type character varying 0 -stock_snapshots captured_at timestamp with time zone 0 -stock_snapshots sync_source character varying 0 -supplier_attribute_definitions id uuid 0 -supplier_attribute_definitions organization_id uuid 0 -supplier_attribute_definitions supplier_id uuid 0 -supplier_attribute_definitions api_attribute_key character varying 0 -supplier_attribute_definitions api_attribute_name character varying 0 -supplier_attribute_definitions api_attribute_name_original character varying 0 -supplier_attribute_definitions data_type character varying 0 -supplier_attribute_definitions api_description text 0 -supplier_attribute_definitions api_unit character varying 0 -supplier_attribute_definitions api_raw_data jsonb 0 -supplier_attribute_definitions source character varying 0 -supplier_attribute_definitions sample_values jsonb 0 -supplier_attribute_definitions value_count integer 0 -supplier_attribute_definitions is_active boolean 0 -supplier_attribute_definitions is_mapped boolean 0 -supplier_attribute_definitions notes text 0 -supplier_attribute_definitions created_at timestamp with time zone 0 -supplier_attribute_definitions updated_at timestamp with time zone 0 -supplier_branches id uuid 0 -supplier_branches supplier_id uuid 0 -supplier_branches branch_name character varying 0 -supplier_branches branch_code character varying 0 -supplier_branches cnpj character varying 0 -supplier_branches inscricao_estadual character varying 0 -supplier_branches state_uf character varying 0 -supplier_branches city character varying 0 -supplier_branches address text 0 -supplier_branches tax_regime character varying 0 -supplier_branches icms_internal_rate numeric 0 -supplier_branches icms_interstate_rate numeric 0 -supplier_branches default_cfop_internal character varying 0 -supplier_branches default_cfop_interstate character varying 0 -supplier_branches is_default boolean 0 -supplier_branches is_active boolean 0 -supplier_branches notes text 0 -supplier_branches created_at timestamp with time zone 0 -supplier_branches updated_at timestamp with time zone 0 -supplier_branches default_cst character varying 0 -supplier_branches default_pis_rate numeric 0 -supplier_branches default_cofins_rate numeric 0 -supplier_branches default_csosn character varying 0 -supplier_branches default_cest character varying 0 -supplier_branches default_operation_nature character varying 0 -supplier_categories id uuid 0 -supplier_categories supplier_id uuid 0 -supplier_categories supplier_code text 0 -supplier_categories supplier_name text 0 -supplier_categories parent_code text 0 -supplier_categories level integer 0 -supplier_categories raw_data jsonb 0 -supplier_categories created_at timestamp with time zone 0 -supplier_category_mappings id uuid 0 -supplier_category_mappings supplier_category_id uuid 0 -supplier_category_mappings category_id uuid 0 -supplier_category_mappings confidence numeric 0 -supplier_category_mappings created_at timestamp with time zone 0 -supplier_colors id uuid 0 -supplier_colors organization_id uuid 0 -supplier_colors supplier_id uuid 0 -supplier_colors name character varying 0 -supplier_colors code character varying 0 -supplier_colors hex_code character varying 0 -supplier_colors api_color_id character varying 0 -supplier_colors api_description text 0 -supplier_colors api_optional_description text 0 -supplier_colors api_raw_data jsonb 0 -supplier_colors image_url text 0 -supplier_colors source character varying 0 -supplier_colors is_active boolean 0 -supplier_colors is_available boolean 0 -supplier_colors notes text 0 -supplier_colors created_at timestamp with time zone 0 -supplier_colors updated_at timestamp with time zone 0 -supplier_colors color_variation_id uuid 0 -supplier_field_mappings id uuid 0 -supplier_field_mappings supplier_id uuid 0 -supplier_field_mappings source_field character varying 0 -supplier_field_mappings source_path character varying 0 -supplier_field_mappings target_table character varying 0 -supplier_field_mappings target_field character varying 0 -supplier_field_mappings transform_type character varying 0 -supplier_field_mappings transform_config jsonb 0 -supplier_field_mappings source_unit character varying 0 -supplier_field_mappings target_unit character varying 0 -supplier_field_mappings is_active boolean 0 -supplier_field_mappings priority integer 0 -supplier_field_mappings created_at timestamp with time zone 0 -supplier_field_mappings updated_at timestamp with time zone 0 -supplier_field_mappings created_by uuid 0 -supplier_field_priority id uuid 0 -supplier_field_priority target_table character varying 0 -supplier_field_priority target_field character varying 0 -supplier_field_priority supplier_id uuid 0 -supplier_field_priority priority integer 0 -supplier_field_priority condition_config jsonb 0 -supplier_field_priority is_active boolean 0 -supplier_field_priority created_at timestamp with time zone 0 -supplier_image_requirements id uuid 0 -supplier_image_requirements supplier_code character varying 0 -supplier_image_requirements supplier_name character varying 0 -supplier_image_requirements min_width_px integer 0 -supplier_image_requirements min_height_px integer 0 -supplier_image_requirements ideal_width_px integer 0 -supplier_image_requirements ideal_height_px integer 0 -supplier_image_requirements max_width_px integer 0 -supplier_image_requirements max_height_px integer 0 -supplier_image_requirements max_file_size_mb numeric 0 -supplier_image_requirements allowed_formats ARRAY 0 -supplier_image_requirements preferred_format character varying 0 -supplier_image_requirements min_quality_score integer 0 -supplier_image_requirements require_white_background boolean 0 -supplier_image_requirements require_square_aspect boolean 0 -supplier_image_requirements aspect_ratio_tolerance numeric 0 -supplier_image_requirements notes text 0 -supplier_image_requirements is_active boolean 0 -supplier_image_requirements created_at timestamp with time zone 0 -supplier_image_requirements updated_at timestamp with time zone 0 -supplier_image_suffix_mappings id uuid 0 -supplier_image_suffix_mappings supplier_suffix_id uuid 0 -supplier_image_suffix_mappings supplier_code character varying 0 -supplier_image_suffix_mappings suffix_pattern character varying 0 -supplier_image_suffix_mappings image_type_id uuid 0 -supplier_image_suffix_mappings image_type_code character varying 0 -supplier_image_suffix_mappings display_priority_override smallint 0 -supplier_image_suffix_mappings gallery_order_override smallint 0 -supplier_image_suffix_mappings force_color_specific boolean 0 -supplier_image_suffix_mappings force_primary_candidate boolean 0 -supplier_image_suffix_mappings mapping_notes text 0 -supplier_image_suffix_mappings confidence_score smallint 0 -supplier_image_suffix_mappings is_validated boolean 0 -supplier_image_suffix_mappings validated_by character varying 0 -supplier_image_suffix_mappings validated_at timestamp with time zone 0 -supplier_image_suffix_mappings is_active boolean 0 -supplier_image_suffix_mappings created_at timestamp with time zone 0 -supplier_image_suffix_mappings updated_at timestamp with time zone 0 -supplier_image_suffix_patterns id uuid 0 -supplier_image_suffix_patterns supplier_id uuid 0 -supplier_image_suffix_patterns supplier_code character varying 0 -supplier_image_suffix_patterns suffix_pattern character varying 0 -supplier_image_suffix_patterns suffix_regex character varying 0 -supplier_image_suffix_patterns supplier_name character varying 0 -supplier_image_suffix_patterns supplier_description text 0 -supplier_image_suffix_patterns file_format character varying 0 -supplier_image_suffix_patterns is_color_specific boolean 0 -supplier_image_suffix_patterns api_fields jsonb 0 -supplier_image_suffix_patterns example_filename character varying 0 -supplier_image_suffix_patterns example_url text 0 -supplier_image_suffix_patterns images_count integer 0 -supplier_image_suffix_patterns last_analysis_at timestamp with time zone 0 -supplier_image_suffix_patterns is_active boolean 0 -supplier_image_suffix_patterns created_at timestamp with time zone 0 -supplier_image_suffix_patterns updated_at timestamp with time zone 0 -supplier_import_batches id uuid 0 -supplier_import_batches supplier_id uuid 0 -supplier_import_batches started_at timestamp with time zone 0 -supplier_import_batches finished_at timestamp with time zone 0 -supplier_import_batches status character varying 0 -supplier_import_batches products_imported integer 0 -supplier_import_batches products_updated integer 0 -supplier_import_batches products_errors integer 0 -supplier_import_batches variants_imported integer 0 -supplier_import_batches variants_updated integer 0 -supplier_import_batches variants_errors integer 0 -supplier_import_batches error_log jsonb 0 -supplier_import_batches notes text 0 -supplier_import_batches created_at timestamp with time zone 0 -supplier_materials id uuid 0 -supplier_materials supplier_id uuid 0 -supplier_materials api_material_key character varying 0 -supplier_materials api_material_name character varying 0 -supplier_materials api_material_group character varying 0 -supplier_materials raw_data jsonb 0 -supplier_materials created_at timestamp with time zone 0 -supplier_materials updated_at timestamp with time zone 0 -supplier_packagings id uuid 0 -supplier_packagings supplier_id uuid 0 -supplier_packagings packaging_id uuid 0 -supplier_packagings supplier_code character varying 0 -supplier_packagings supplier_name character varying 0 -supplier_packagings unit_price numeric 0 -supplier_packagings currency character varying 0 -supplier_packagings min_order_quantity integer 0 -supplier_packagings is_available boolean 0 -supplier_packagings lead_time_days integer 0 -supplier_packagings last_price_update timestamp with time zone 0 -supplier_packagings active boolean 0 -supplier_packagings created_at timestamp with time zone 0 -supplier_packagings updated_at timestamp with time zone 0 -supplier_products_raw id uuid 0 -supplier_products_raw supplier_id uuid 0 -supplier_products_raw supplier_reference character varying 0 -supplier_products_raw raw_data jsonb 0 -supplier_products_raw raw_hash character varying 0 -supplier_products_raw imported_at timestamp with time zone 0 -supplier_products_raw import_batch_id uuid 0 -supplier_products_raw processed boolean 0 -supplier_products_raw processed_at timestamp with time zone 0 -supplier_products_raw process_errors jsonb 0 -supplier_products_raw product_id uuid 0 -supplier_products_raw created_at timestamp with time zone 0 -supplier_products_raw updated_at timestamp with time zone 0 -supplier_products_raw images_processed boolean 0 -supplier_products_raw supplier_sku character varying 0 -supplier_products_raw variant_id uuid 0 -supplier_property_mappings id uuid 0 -supplier_property_mappings supplier_id uuid 0 -supplier_property_mappings supplier_code text 0 -supplier_property_mappings raw_pattern text 0 -supplier_property_mappings property_code text 0 -supplier_property_mappings priority integer 0 -supplier_property_mappings notes text 0 -supplier_property_mappings active boolean 0 -supplier_property_mappings created_at timestamp with time zone 0 -supplier_property_mappings updated_at timestamp with time zone 0 -supplier_settings id uuid 0 -supplier_settings supplier_id uuid 0 -supplier_settings default_supply_mode character varying 0 -supplier_settings default_is_imported boolean 0 -supplier_settings use_origin_country boolean 0 -supplier_settings default_lead_time_days integer 0 -supplier_settings auto_sync_enabled boolean 0 -supplier_settings created_at timestamp with time zone 0 -supplier_settings updated_at timestamp with time zone 0 -supplier_settings default_packing_classification character varying 0 -supplier_technique_mappings id uuid 0 -supplier_technique_mappings supplier_technique_name text 0 -supplier_technique_mappings supplier_technique_code character varying 0 -supplier_technique_mappings supplier_name character varying 0 -supplier_technique_mappings target_technique_id uuid 0 -supplier_technique_mappings confidence numeric 0 -supplier_technique_mappings is_verified boolean 0 -supplier_technique_mappings mapped_by character varying 0 -supplier_technique_mappings created_at timestamp with time zone 0 -supplier_technique_mappings updated_at timestamp with time zone 0 -supplier_unit_conversions id uuid 0 -supplier_unit_conversions supplier_id uuid 0 -supplier_unit_conversions source_unit character varying 0 -supplier_unit_conversions target_unit character varying 0 -supplier_unit_conversions multiplier numeric 0 -supplier_unit_conversions unit_type character varying 0 -supplier_unit_conversions description character varying 0 -supplier_unit_conversions is_default boolean 0 -supplier_unit_conversions is_active boolean 0 -supplier_unit_conversions created_at timestamp with time zone 0 -supplier_value_mappings id uuid 0 -supplier_value_mappings supplier_id uuid 0 -supplier_value_mappings field_type character varying 0 -supplier_value_mappings source_value character varying 0 -supplier_value_mappings target_value character varying 0 -supplier_value_mappings display_value character varying 0 -supplier_value_mappings sort_order integer 0 -supplier_value_mappings is_active boolean 0 -supplier_value_mappings created_at timestamp with time zone 0 -supplier_value_mappings updated_at timestamp with time zone 0 -supplier_video_requirements id uuid 0 -supplier_video_requirements supplier_code character varying 0 -supplier_video_requirements supplier_name character varying 0 -supplier_video_requirements min_width_px integer 0 -supplier_video_requirements min_height_px integer 0 -supplier_video_requirements ideal_width_px integer 0 -supplier_video_requirements ideal_height_px integer 0 -supplier_video_requirements min_duration_seconds integer 0 -supplier_video_requirements max_duration_seconds integer 0 -supplier_video_requirements max_file_size_mb numeric 0 -supplier_video_requirements allowed_formats ARRAY 0 -supplier_video_requirements notes text 0 -supplier_video_requirements is_active boolean 0 -supplier_video_requirements created_at timestamp with time zone 0 -supplier_video_requirements updated_at timestamp with time zone 0 -suppliers id uuid 0 -suppliers name text 0 -suppliers code text 0 -suppliers cnpj text 0 -suppliers contact_name text 0 -suppliers email text 0 -suppliers phone text 0 -suppliers address text 0 -suppliers active boolean 0 -suppliers created_at timestamp with time zone 0 -suppliers website text 0 -suppliers contact_person text 0 -suppliers payment_terms text 0 -suppliers delivery_time_days integer 0 -suppliers minimum_order_value numeric 0 -suppliers notes text 0 -suppliers updated_at timestamp with time zone 0 -suppliers organization_id uuid 0 -suppliers trading_name character varying 0 -suppliers api_type character varying 0 -suppliers api_base_url text 0 -suppliers api_credentials jsonb 0 -suppliers min_order_value numeric 0 -suppliers shipping_terms text 0 -suppliers default_markup_percent numeric 0 -suppliers is_product_supplier boolean 0 -suppliers is_engraving_supplier boolean 0 -suppliers sync_enabled boolean 0 -suppliers sync_interval_minutes integer 0 -suppliers last_full_sync_at timestamp with time zone 0 -suppliers last_sync_status character varying 0 -suppliers last_sync_error text 0 -suppliers api_rate_limit_per_hour integer 0 -suppliers priority integer 0 -suppliers api_requests_current_hour integer 0 -suppliers api_requests_reset_at timestamp with time zone 0 -suppliers state_uf character varying 0 -suppliers tax_regime character varying 0 -suppliers inscricao_estadual character varying 0 -suppliers phone2 character varying 0 -suppliers logo_url text 0 -system_changelog id uuid 0 -system_changelog executed_at timestamp with time zone 0 -system_changelog step_number integer 0 -system_changelog category text 0 -system_changelog title text 0 -system_changelog description text 0 -system_changelog impact text 0 -system_changelog executed_by text 0 -system_changelog metadata jsonb 0 -system_documentation id uuid 0 -system_documentation modulo character varying 0 -system_documentation tabela character varying 0 -system_documentation proposito text 0 -system_documentation relaciona_com ARRAY 0 -system_documentation observacoes text 0 -system_documentation criado_em timestamp with time zone 0 -system_documentation atualizado_em timestamp with time zone 0 -system_settings key text 0 -system_settings value jsonb 0 -system_settings updated_by uuid 0 -system_settings updated_at timestamp with time zone 0 -system_settings description text 0 -system_settings_legacy id uuid 0 -system_settings_legacy setting_key text 0 -system_settings_legacy setting_value jsonb 0 -system_settings_legacy description text 0 -system_settings_legacy is_public boolean 0 -system_settings_legacy created_at timestamp with time zone 0 -system_settings_legacy updated_at timestamp with time zone 0 -system_settings_legacy category text 0 -system_settings_legacy is_secret boolean 0 -tabela_preco_gravacao_oficial id uuid 0 -tabela_preco_gravacao_oficial codigo_tabela character varying 0 -tabela_preco_gravacao_oficial nome character varying 0 -tabela_preco_gravacao_oficial descricao text 0 -tabela_preco_gravacao_oficial cobra_por_cor boolean 0 -tabela_preco_gravacao_oficial max_cores integer 0 -tabela_preco_gravacao_oficial desconto_segunda_cor numeric 0 -tabela_preco_gravacao_oficial desconto_terceira_cor numeric 0 -tabela_preco_gravacao_oficial custo_setup numeric 0 -tabela_preco_gravacao_oficial custo_setup_por_cor boolean 0 -tabela_preco_gravacao_oficial custo_manuseio numeric 0 -tabela_preco_gravacao_oficial ativo boolean 0 -tabela_preco_gravacao_oficial created_at timestamp with time zone 0 -tabela_preco_gravacao_oficial updated_at timestamp with time zone 0 -tabela_preco_gravacao_oficial custo_aplicacao numeric 0 -tabela_preco_gravacao_oficial custo_termo_transferencia numeric 0 -tabela_preco_gravacao_oficial custo_queima_forno numeric 0 -tabela_preco_gravacao_oficial codigo_curto character varying 0 -tabela_preco_gravacao_oficial grupo_tecnica character varying 0 -tabela_preco_gravacao_oficial usa_faixa_dimensional boolean 0 -tabela_preco_gravacao_oficial opcoes_modificadores jsonb 0 -tabela_preco_gravacao_oficial tom_options jsonb 0 -tabela_preco_gravacao_oficial markup_percent numeric 0 -tabela_preco_gravacao_oficial preco_minimo_unitario numeric 0 -tabela_preco_gravacao_oficial preco_maximo_unitario numeric 0 -tabela_preco_gravacao_oficial_faixa id uuid 0 -tabela_preco_gravacao_oficial_faixa tabela_preco_gravacao_id uuid 0 -tabela_preco_gravacao_oficial_faixa quantidade_minima integer 0 -tabela_preco_gravacao_oficial_faixa quantidade_maxima integer 0 -tabela_preco_gravacao_oficial_faixa preco_unitario numeric 0 -tabela_preco_gravacao_oficial_faixa prazo_dias integer 0 -tabela_preco_gravacao_oficial_faixa ordem integer 0 -tabela_preco_gravacao_oficial_faixa created_at timestamp with time zone 0 -tabela_preco_gravacao_oficial_faixa updated_at timestamp with time zone 0 -tabela_preco_gravacao_oficial_faixa largura_min numeric 0 -tabela_preco_gravacao_oficial_faixa largura_max numeric 0 -tabela_preco_gravacao_oficial_faixa altura_min numeric 0 -tabela_preco_gravacao_oficial_faixa altura_max numeric 0 -tags id uuid 0 -tags organization_id uuid 0 -tags name character varying 0 -tags slug character varying 0 -tags color_hex character varying 0 -tags description text 0 -tags usage_count integer 0 -tags is_active boolean 0 -tags created_at timestamp with time zone 0 -tags updated_at timestamp with time zone 0 -target_audiences id uuid 0 -target_audiences organization_id uuid 0 -target_audiences name character varying 0 -target_audiences slug character varying 0 -target_audiences description text 0 -target_audiences category character varying 0 -target_audiences icon_name character varying 0 -target_audiences color_hex character varying 0 -target_audiences display_order integer 0 -target_audiences is_active boolean 0 -target_audiences created_at timestamp with time zone 0 -target_audiences updated_at timestamp with time zone 0 -tecnicas_gravacao codigo character varying 0 -tecnicas_gravacao nome character varying 0 -tecnicas_gravacao slug character varying 0 -tecnicas_gravacao ativo boolean 0 -tecnicas_gravacao ordem_exibicao integer 0 -tecnicas_gravacao created_at timestamp with time zone 0 -tecnicas_gravacao updated_at timestamp with time zone 0 -user_comparisons id uuid 0 -user_comparisons user_id uuid 0 -user_comparisons client_id text 0 -user_comparisons client_name text 0 -user_comparisons name text 0 -user_comparisons items jsonb 0 -user_comparisons share_token text 0 -user_comparisons is_public boolean 0 -user_comparisons share_expires_at timestamp with time zone 0 -user_comparisons view_count integer 0 -user_comparisons created_at timestamp with time zone 0 -user_comparisons updated_at timestamp with time zone 0 -user_favorites id uuid 0 -user_favorites user_id uuid 0 -user_favorites product_id uuid 0 -user_favorites notes text 0 -user_favorites created_at timestamp with time zone 0 -user_filter_presets id uuid 0 -user_filter_presets user_id uuid 0 -user_filter_presets preset_name text 0 -user_filter_presets filters jsonb 0 -user_filter_presets is_global boolean 0 -user_filter_presets usage_count integer 0 -user_filter_presets created_at timestamp with time zone 0 -user_filter_presets updated_at timestamp with time zone 0 -user_known_devices id uuid 0 -user_known_devices user_id uuid 0 -user_known_devices fingerprint text 0 -user_known_devices device_name text 0 -user_known_devices last_seen_at timestamp with time zone 0 -user_known_devices created_at timestamp with time zone 0 -user_onboarding id uuid 0 -user_onboarding user_id uuid 0 -user_onboarding has_completed_tour boolean 0 -user_onboarding current_step integer 0 -user_onboarding completed_steps jsonb 0 -user_onboarding started_at timestamp with time zone 0 -user_onboarding completed_at timestamp with time zone 0 -user_onboarding created_at timestamp with time zone 0 -user_onboarding updated_at timestamp with time zone 0 -user_organizations organization_id uuid 0 -user_organizations user_id uuid 0 -user_organizations role USER-DEFINED 0 -user_organizations created_at timestamp with time zone 0 -user_organizations updated_at timestamp with time zone 0 -user_preferences id uuid 0 -user_preferences user_id uuid 0 -user_preferences comparison_weights jsonb 0 -user_preferences comparison_column_order jsonb 0 -user_preferences created_at timestamp with time zone 0 -user_preferences updated_at timestamp with time zone 0 -user_preferences filter_states jsonb 0 -user_roles user_id uuid 0 -user_roles role USER-DEFINED 0 -user_roles created_at timestamp with time zone 0 -user_roles granted_by uuid 0 -user_search_history id uuid 0 -user_search_history user_id uuid 0 -user_search_history query_text text 0 -user_search_history history_type text 0 -user_search_history result_count integer 0 -user_search_history is_pinned boolean 0 -user_search_history metadata jsonb 0 -user_search_history created_at timestamp with time zone 0 -user_search_history updated_at timestamp with time zone 0 -user_token_revocations user_id uuid 0 -user_token_revocations revoked_at timestamp with time zone 0 -variant_commemorative_dates id uuid 0 -variant_commemorative_dates product_id uuid 0 -variant_commemorative_dates variant_id uuid 0 -variant_commemorative_dates commemorative_date_id uuid 0 -variant_commemorative_dates display_order integer 0 -variant_commemorative_dates is_featured boolean 0 -variant_commemorative_dates custom_description text 0 -variant_commemorative_dates created_by uuid 0 -variant_commemorative_dates created_at timestamp with time zone 0 -variant_supplier_sources id uuid 0 -variant_supplier_sources organization_id uuid 0 -variant_supplier_sources variant_id uuid 0 -variant_supplier_sources supplier_id uuid 0 -variant_supplier_sources quantity integer 0 -variant_supplier_sources source character varying 0 -variant_supplier_sources last_synced_at timestamp with time zone 0 -variant_supplier_sources sync_status character varying 0 -variant_supplier_sources sync_error text 0 -variant_supplier_sources updated_at timestamp with time zone 0 -variant_supplier_sources next_quantity_1 integer 0 -variant_supplier_sources next_date_1 date 0 -variant_supplier_sources next_quantity_2 integer 0 -variant_supplier_sources next_date_2 date 0 -variant_supplier_sources next_quantity_3 integer 0 -variant_supplier_sources next_date_3 date 0 -variant_supplier_sources supplier_sku character varying 0 -variant_supplier_sources supplier_color_code character varying 0 -variant_supplier_sources supplier_color_name character varying 0 -variant_supplier_sources cost_price numeric 0 -variant_supplier_sources list_price numeric 0 -variant_supplier_sources cost_price_1 numeric 0 -variant_supplier_sources min_qty_1 integer 0 -variant_supplier_sources cost_price_2 numeric 0 -variant_supplier_sources min_qty_2 integer 0 -variant_supplier_sources cost_price_3 numeric 0 -variant_supplier_sources min_qty_3 integer 0 -variant_supplier_sources cost_price_4 numeric 0 -variant_supplier_sources min_qty_4 integer 0 -variant_supplier_sources cost_price_5 numeric 0 -variant_supplier_sources min_qty_5 integer 0 -variant_supplier_sources lead_time_days integer 0 -variant_supplier_sources min_order_qty integer 0 -variant_supplier_sources pack_quantity integer 0 -variant_supplier_sources sale_multiplier integer 0 -variant_supplier_sources is_preferred boolean 0 -variant_supplier_sources priority integer 0 -variant_supplier_sources is_active boolean 0 -variant_supplier_sources supplier_images jsonb 0 -variant_supplier_sources supplier_videos jsonb 0 -variant_supplier_sources supplier_thumbnail character varying 0 -variant_supplier_sources removed_from_api boolean 0 -variant_supplier_sources removed_at timestamp with time zone 0 -variant_supplier_sources raw_data jsonb 0 -variant_supplier_sources stock_main_warehouse integer 0 -variant_supplier_sources stock_other_warehouses integer 0 -variant_supplier_sources supplier_availability_status character varying 0 -variant_supplier_sources supplier_ipi_rate numeric 0 -variant_supplier_sources supplier_branch_id uuid 0 -variant_supplier_sources icms_rate numeric 0 -variant_supplier_sources pis_rate numeric 0 -variant_supplier_sources cofins_rate numeric 0 -variant_supplier_sources cfop character varying 0 -variant_supplier_sources csosn character varying 0 -variant_supplier_sources cest character varying 0 -variant_supplier_sources cst character varying 0 -variant_supplier_sources operation_nature character varying 0 -variant_supplier_sources price_updated_at timestamp with time zone 0 -variation_types id uuid 0 -variation_types organization_id uuid 0 -variation_types code character varying 0 -variation_types name character varying 0 -variation_types slug character varying 0 -variation_types description text 0 -variation_types is_required boolean 0 -variation_types value_type character varying 0 -variation_types sort_order integer 0 -variation_types is_active boolean 0 -variation_types created_at timestamp with time zone 0 -variation_types updated_at timestamp with time zone 0 -variation_values id uuid 0 -variation_values organization_id uuid 0 -variation_values variation_type_id uuid 0 -variation_values value character varying 0 -variation_values label character varying 0 -variation_values description text 0 -variation_values sort_order integer 0 -variation_values is_active boolean 0 -variation_values created_at timestamp with time zone 0 -variation_values updated_at timestamp with time zone 0 -video_import_queue id uuid 0 -video_import_queue youtube_id text 0 -video_import_queue youtube_url text 0 -video_import_queue source_supplier text 0 -video_import_queue product_references ARRAY 0 -video_import_queue status text 0 -video_import_queue local_file_path text 0 -video_import_queue file_size_bytes bigint 0 -video_import_queue cloudflare_video_id text 0 -video_import_queue cloudflare_thumbnail text 0 -video_import_queue cloudflare_hls text 0 -video_import_queue cloudflare_embed text 0 -video_import_queue cloudflare_duration_seconds integer 0 -video_import_queue retry_count integer 0 -video_import_queue max_retries integer 0 -video_import_queue last_error text 0 -video_import_queue created_at timestamp with time zone 0 -video_import_queue updated_at timestamp with time zone 0 -video_import_queue started_at timestamp with time zone 0 -video_import_queue completed_at timestamp with time zone 0 -video_types id uuid 0 -video_types code character varying 0 -video_types name character varying 0 -video_types name_en character varying 0 -video_types description text 0 -video_types category character varying 0 -video_types autoplay boolean 0 -video_types muted boolean 0 -video_types loop boolean 0 -video_types controls boolean 0 -video_types display_priority integer 0 -video_types icon character varying 0 -video_types is_active boolean 0 -video_types created_at timestamp with time zone 0 -video_types updated_at timestamp with time zone 0 -video_validation_log id uuid 0 -video_validation_log video_id uuid 0 -video_validation_log product_id uuid 0 -video_validation_log cloudflare_video_id character varying 0 -video_validation_log supplier_code character varying 0 -video_validation_log width_px integer 0 -video_validation_log height_px integer 0 -video_validation_log duration_seconds integer 0 -video_validation_log file_size_bytes bigint 0 -video_validation_log validation_status character varying 0 -video_validation_log is_valid boolean 0 -video_validation_log issues ARRAY 0 -video_validation_log recommendations ARRAY 0 -video_validation_log action_type character varying 0 -video_validation_log validated_at timestamp with time zone 0 -video_variant_links id uuid 0 -video_variant_links video_id text 0 -video_variant_links variant_id text 0 -video_variant_links variant_name text 0 -video_variant_links variant_color_hex text 0 -video_variant_links supplier_code text 0 -video_variant_links product_id text 0 -video_variant_links created_at timestamp with time zone 0 -voice_command_logs id uuid 0 -voice_command_logs user_id uuid 0 -voice_command_logs transcript text 0 -voice_command_logs action text 0 -voice_command_logs response text 0 -voice_command_logs data jsonb 0 -voice_command_logs duration_ms integer 0 -voice_command_logs success boolean 0 -voice_command_logs created_at timestamp with time zone 0 -webhook_deliveries id uuid 0 -webhook_deliveries webhook_id uuid 0 -webhook_deliveries event text 0 -webhook_deliveries payload jsonb 0 -webhook_deliveries payload_hash text 0 -webhook_deliveries status_code integer 0 -webhook_deliveries response_body_truncated text 0 -webhook_deliveries attempt integer 0 -webhook_deliveries success boolean 0 -webhook_deliveries error_message text 0 -webhook_deliveries delivered_at timestamp with time zone 0 -webhook_delivery_metrics id uuid 0 -webhook_delivery_metrics request_id text 0 -webhook_delivery_metrics event_type text 0 -webhook_delivery_metrics source text 0 -webhook_delivery_metrics direction text 0 -webhook_delivery_metrics endpoint text 0 -webhook_delivery_metrics http_status integer 0 -webhook_delivery_metrics duration_ms integer 0 -webhook_delivery_metrics attempt integer 0 -webhook_delivery_metrics success boolean 0 -webhook_delivery_metrics error_class text 0 -webhook_delivery_metrics error_message text 0 -webhook_delivery_metrics payload_bytes integer 0 -webhook_delivery_metrics metadata jsonb 0 -webhook_delivery_metrics occurred_at timestamp with time zone 0 -workspace_notifications id uuid 0 -workspace_notifications user_id uuid 0 -workspace_notifications title text 0 -workspace_notifications message text 0 -workspace_notifications type text 0 -workspace_notifications category text 0 -workspace_notifications is_read boolean 0 -workspace_notifications action_url text 0 -workspace_notifications metadata jsonb 0 -workspace_notifications created_at timestamp with time zone 0 -xbz_gallery_staging id uuid 0 -xbz_gallery_staging codigo_amigavel text 0 -xbz_gallery_staging d_number integer 0 -xbz_gallery_staging url_original text 0 -xbz_gallery_staging cloudflare_image_id text 0 -xbz_gallery_staging url_cdn text 0 -xbz_gallery_staging product_id uuid 0 -xbz_gallery_staging http_status integer 0 -xbz_gallery_staging file_size_bytes bigint 0 -xbz_gallery_staging status text 0 -xbz_gallery_staging error_message text 0 -xbz_gallery_staging created_at timestamp with time zone 0 -xbz_gallery_staging updated_at timestamp with time zone 0 diff --git a/docs/historico/AUDITORIA_REDEPLOY_PROMO_GIFTS_2026-05-13_15-32.md b/docs/historico/AUDITORIA_REDEPLOY_PROMO_GIFTS_2026-05-13_15-32.md deleted file mode 100644 index 2e4140c17..000000000 --- a/docs/historico/AUDITORIA_REDEPLOY_PROMO_GIFTS_2026-05-13_15-32.md +++ /dev/null @@ -1,602 +0,0 @@ -# Auditoria e Plano Pedagógico de Redeploy — Promo_Gifts - -**Arquivo gerado em:** 13/05/2026 15:32:16 -03 -**Repositório:** `adm01-debug/Promo_Gifts` -**Projeto Supabase:** `doufsxqlfjyuvxuezpln` -**Objetivo:** consolidar, de forma pedagógica, a auditoria técnica e o plano de correções antes do redeploy seguro. - ---- - -## 1. Resumo executivo - -O projeto `Promo_Gifts` está bem estruturado e já possui integração com Supabase, GitHub Actions, Edge Functions, migrations, testes e scripts de segurança. O projeto Supabase correto é: - -```txt -doufsxqlfjyuvxuezpln -``` - -A URL pública usada pelo frontend deve ser: - -```env -VITE_SUPABASE_URL=https://doufsxqlfjyuvxuezpln.supabase.co -``` - -Os secrets principais já configurados no GitHub Actions foram: - -```txt -SUPABASE_ACCESS_TOKEN -SUPABASE_SERVICE_ROLE_KEY -VITE_SUPABASE_PUBLISHABLE_KEY -VITE_SUPABASE_URL -``` - -Apesar disso, o projeto ainda não deve ir para redeploy final de produção sem antes corrigir os pontos de segurança listados neste documento. - ---- - -## 2. Veredito atual - -```txt -Status atual: NO-GO temporário para produção -Motivo: funções públicas sensíveis precisam de proteção extra antes do redeploy final -``` - -O projeto pode avançar para dry-run, CI, build, testes, preparação de branch e revisão de patch. - -Ainda não deve avançar para redeploy final em produção, `supabase db push` sem drift check ou deploy final de todas as Edge Functions sem hardening. - ---- - -## 3. Bloqueadores principais - -### 3.1 `webhook-dispatcher` - -A função `webhook-dispatcher` está pública via `verify_jwt = false` e usa `SUPABASE_SERVICE_ROLE_KEY`. Ela recebe `event`, `payload`, `replay_delivery_id`, `test_mode` e `test_webhook_id`. - -Risco: se alguém descobrir a URL, pode tentar disparar eventos, replays ou testes de webhook. - -Correção recomendada: - -```txt -Adicionar WEBHOOK_DISPATCHER_SECRET -Exigir header x-dispatcher-secret -Rejeitar chamada sem secret -``` - -### 3.2 `connections-auto-test` - -A função `connections-auto-test` está pública via `verify_jwt = false`. Ela foi criada para cron e retesta conexões ativas em `external_connections`. - -Risco: se acionada publicamente, pode gerar ruído operacional, chamadas externas, logs, custo ou exposição indireta de status de credenciais. - -Correção recomendada: - -```txt -Adicionar CONNECTIONS_AUTO_TEST_SECRET -Exigir header x-cron-secret -Rejeitar chamada sem secret -``` - -### 3.3 Migrations sem drift check - -A pasta `supabase/migrations` contém muitos arquivos, incluindo schemas completos, arquivos `FIXED`, migrations antigas de RLS, seeds, testes SQL e migrations pequenas com nomes UUID. - -Exemplos de risco: - -```txt -20250103_05_rls_remaining.sql -20250103_05_rls_remaining_FIXED.sql -20250103_complete_schema.sql -20250103_schema_no_gamification.sql -20250103_rls_policies.sql -20250103_rls_no_gamification.sql -``` - -Conclusão: não rodar `supabase db push` às cegas. - -Antes de qualquer mudança de banco: - -```bash -npx supabase link --project-ref doufsxqlfjyuvxuezpln -npx supabase migration list -npx supabase db diff --linked -``` - -Se o diff vier grande ou inesperado, parar e revisar. - ---- - -## 4. Edge Functions auditadas - -### 4.1 `e2e-cleanup` - -Função destrutiva, mas com boa proteção. - -Pontos positivos: - -```txt -Usa x-e2e-cleanup-token -Compara com E2E_CLEANUP_TOKEN -Possui allowlist E2E_CLEANUP_ALLOWED_EMAILS -dryRun = true por padrão -Resolve user_id server-side -Não apaga auth.users -Registra auditoria -``` - -Condição para produção: - -```txt -E2E_CLEANUP_ALLOWED_EMAILS deve conter apenas e-mails de teste. -E2E_CLEANUP_TOKEN deve ser forte, longo e secreto. -dryRun=false só deve ser usado com revisão. -``` - -Classificação: aceitável com condição. - -### 4.2 `connections-auto-test` - -Função sensível. - -Pontos positivos: - -```txt -Processa conexões em batch -Tem retry/backoff -Usa service client -Busca apenas conexões active + auto_test_enabled -``` - -Problema: não foi identificada autenticação própria obrigatória. - -Classificação: bloqueador de produção até adicionar `CONNECTIONS_AUTO_TEST_SECRET`. - -### 4.3 `image-proxy` - -Função de proxy de imagem. - -Pontos positivos: - -```txt -Possui allowlist de domínio -Não é proxy aberto genérico -Usa bot/rate protection -Usa circuit breaker -Define cache-control -``` - -Ajustes recomendados: - -```txt -localhost/127.0.0.1 somente em development -Validar Content-Type começando com image/ -Limitar tamanho máximo da resposta -``` - -Classificação: aceitável com ajustes menores. - -### 4.4 `webhook-inbound` - -Função bem estruturada para receber webhooks externos. - -Pontos positivos: - -```txt -Busca endpoint ativo por slug -Usa hmac_secret_ref -Valida x-signature-256 ou x-webhook-signature -Calcula HMAC SHA-256 -Usa comparação timing-safe -Registra eventos -Retorna 401 quando assinatura é inválida -``` - -Melhoria futura: adicionar anti-replay com timestamp + nonce/event_id. - -Classificação: aceitável se todos os HMAC secrets estiverem configurados. - -### 4.5 `webhook-dispatcher` - -Função sensível. - -Pontos positivos: - -```txt -Assina payload com HMAC -Registra entregas -Faz retry/backoff -Desativa webhook após falhas consecutivas -``` - -Problema: não foi identificada autenticação própria obrigatória para chamada direta. - -Classificação: bloqueador de produção até adicionar `WEBHOOK_DISPATCHER_SECRET`. - -### 4.6 `crm-db-bridge` - -Função grande e crítica. - -Pontos positivos: - -```txt -Usa CORS centralizado -Usa bot protection -Usa circuit breaker -Usa request-id -Usa resolver centralizado de credenciais -Usa cliente CRM singleton/warm-up -``` - -Riscos: - -```txt -Está pública via verify_jwt = false -Acessa CRM externo -Possui diagnósticos -Pode expor status de credenciais, latência ou configuração se mal protegida -``` - -Classificação: alto risco; precisa revisão de rotas sensíveis antes do redeploy final. - -### 4.7 `mcp-server` - -Função crítica que usa service role e header `x-mcp-key`. - -Pontos positivos: - -```txt -Valida chave via RPC validate_mcp_key -Usa escopos -Registra auditoria -Separa permissões de ferramentas -``` - -Checklist obrigatório: - -```txt -validate_mcp_key deve comparar hash, não plaintext -Chaves MCP não devem ficar em texto puro -Expiração/revogação precisam funcionar -Auditoria precisa estar ativa -Wildcard "*" só deve existir para chaves extremamente restritas -CORS precisa estar restrito -``` - -Classificação: aceitável com validação adicional. - -### 4.8 `ai-recommendations` - -Função pública no Supabase, mas com autenticação própria. - -Pontos positivos: - -```txt -Usa authenticateRequest(req) -Aplica rate limit por usuário -Usa bot protection -Valida entrada com zod -Usa controle de quota AI -Usa LOVABLE_API_KEY server-side -``` - -Classificação: aceitável. - -### 4.9 `quote-public-view` - -Foi encontrada referência em `supabase/config.toml`, mas o arquivo da função não foi encontrado em: - -```txt -supabase/functions/quote-public-view/index.ts -``` - -Possibilidades: - -```txt -A função foi removida e o config ficou obsoleto -A função existe com outro nome -A função deveria existir e está faltando -``` - -Ação recomendada: - -```txt -Buscar chamadas para quote-public-view no frontend. -Se não houver uso, remover do config.toml. -Se houver uso, restaurar a função. -``` - ---- - -## 5. Variáveis e secrets necessários - -### 5.1 Frontend / hosting - -No Lovable, Vercel, Netlify ou outro hosting: - -```env -VITE_SUPABASE_URL=https://doufsxqlfjyuvxuezpln.supabase.co -VITE_SUPABASE_PUBLISHABLE_KEY= -``` - -Nunca colocar `SUPABASE_SERVICE_ROLE_KEY` no frontend. - -### 5.2 GitHub Actions - -No GitHub: - -```env -SUPABASE_ACCESS_TOKEN= -SUPABASE_SERVICE_ROLE_KEY= -VITE_SUPABASE_URL=https://doufsxqlfjyuvxuezpln.supabase.co -VITE_SUPABASE_PUBLISHABLE_KEY= -``` - -### 5.3 Supabase Edge Function Secrets - -No Supabase: - -```env -SUPABASE_SERVICE_ROLE_KEY= -LOVABLE_API_KEY= -E2E_CLEANUP_TOKEN= -E2E_CLEANUP_ALLOWED_EMAILS= -CONNECTIONS_AUTO_TEST_SECRET= -WEBHOOK_DISPATCHER_SECRET= -IMAGE_PROXY_ALLOW_LOCALHOST=false -IMAGE_PROXY_MAX_BYTES=5242880 -``` - -Conforme integrações ativas: - -```env -EXTERNAL_CRM_URL= -EXTERNAL_CRM_SERVICE_ROLE_KEY= -EXTERNAL_CRM_ANON_KEY= -EXTERNAL_SUPABASE_URL= -EXTERNAL_SUPABASE_SERVICE_KEY= -``` - ---- - -## 6. Patch mínimo recomendado - -### 6.1 Proteger `connections-auto-test` - -Adicionar validação de header: - -```txt -Header: x-cron-secret -Secret: CONNECTIONS_AUTO_TEST_SECRET -``` - -Comportamento esperado: - -```txt -Sem secret correto => 401 Unauthorized -Com secret correto => executa normalmente -``` - -### 6.2 Proteger `webhook-dispatcher` - -Adicionar validação de header: - -```txt -Header: x-dispatcher-secret -Secret: WEBHOOK_DISPATCHER_SECRET -``` - -Comportamento esperado: - -```txt -Sem secret correto => 401 Unauthorized -Com secret correto => executa normalmente -``` - -### 6.3 Endurecer `image-proxy` - -Adicionar: - -```txt -Content-Type precisa começar com image/ -Limite máximo de bytes -localhost apenas se IMAGE_PROXY_ALLOW_LOCALHOST=true -``` - -### 6.4 Criar `.env.example` - -Criar arquivo raiz: - -```env -VITE_SUPABASE_URL=https://doufsxqlfjyuvxuezpln.supabase.co -VITE_SUPABASE_PUBLISHABLE_KEY= - -# Server/Edge only - nunca usar no frontend -SUPABASE_SERVICE_ROLE_KEY= -SUPABASE_ACCESS_TOKEN= -CONNECTIONS_AUTO_TEST_SECRET= -WEBHOOK_DISPATCHER_SECRET= -E2E_CLEANUP_TOKEN= -E2E_CLEANUP_ALLOWED_EMAILS= -LOVABLE_API_KEY= -``` - ---- - -## 7. Sequência segura de execução - -### Fase 1 — hardening - -```txt -1. Aplicar proteção em connections-auto-test -2. Aplicar proteção em webhook-dispatcher -3. Criar CONNECTIONS_AUTO_TEST_SECRET no Supabase -4. Criar WEBHOOK_DISPATCHER_SECRET no Supabase -``` - -### Fase 2 — ajustes menores - -```txt -1. Criar .env.example -2. Ajustar image-proxy -3. Remover ou restaurar quote-public-view -4. Revisar diagnósticos de crm-db-bridge -``` - -### Fase 3 — validação local ou CI - -```bash -npm ci -npm run build -npm run typecheck:full -npm run test -npm run test:coverage -npm run smoke -``` - -Se disponível: - -```bash -npm run test:e2e:critical -``` - -### Fase 4 — Supabase drift check - -```bash -npx supabase link --project-ref doufsxqlfjyuvxuezpln -npx supabase migration list -npx supabase db diff --linked -``` - -Regra: se o diff vier inesperado, parar. Não aplicar `db push` sem revisão. - -### Fase 5 — Edge Functions - -No GitHub Actions: - -```txt -Actions > Deploy Edge Functions > Run workflow -``` - -Executar primeiro dry-run, se disponível. - -### Fase 6 — Frontend - -No hosting: - -```txt -Confirmar VITE_SUPABASE_URL -Confirmar VITE_SUPABASE_PUBLISHABLE_KEY -Rodar redeploy da branch main -``` - ---- - -## 8. Checklist final de liberação - -```txt -[ ] WEBHOOK_DISPATCHER_SECRET criado no Supabase -[ ] CONNECTIONS_AUTO_TEST_SECRET criado no Supabase -[ ] webhook-dispatcher rejeita chamada sem secret -[ ] connections-auto-test rejeita chamada sem secret -[ ] e2e-cleanup tem token forte -[ ] e2e-cleanup tem allowlist apenas de e-mails de teste -[ ] webhook-inbound tem HMAC secrets válidos -[ ] quote-public-view foi removido do config ou restaurado -[ ] npm run build passa -[ ] npm run typecheck:full passa -[ ] npm run test passa -[ ] CI passa no GitHub Actions -[ ] deploy Edge Functions passa -[ ] db diff revisado -[ ] nenhuma service role key no frontend -[ ] rollback definido -``` - ---- - -## 9. Plano de rollback - -### Antes do redeploy - -```txt -1. Anotar commit atual da branch main -2. Anotar último deploy funcional do frontend -3. Não aplicar migrations destrutivas -4. Fazer deploy de Edge Functions separado do frontend -5. Validar health checks antes de promover frontend -``` - -### Rollback frontend - -```bash -git revert -git push origin main -``` - -### Rollback Edge Functions - -```bash -git checkout -supabase functions deploy --project-ref doufsxqlfjyuvxuezpln -``` - -### Rollback banco - -Não fazer rollback de banco automaticamente. Rollback de schema só com SQL revisado, porque pode causar perda de dados. - ---- - -## 10. Meta 10/10 - -Para chegar ao estado 10/10: - -```txt -1. Aplicar patch de hardening -2. Criar secrets novos no Supabase -3. Rodar CI completo -4. Rodar build -5. Rodar testes -6. Rodar dry-run de Edge Functions -7. Fazer drift check do banco -8. Revisar quote-public-view -9. Validar hosting frontend -10. Fazer redeploy controlado com rollback pronto -``` - -Critério 10/10: - -```txt -Produção só é liberada quando não houver função pública sensível sem autenticação própria, build/testes estiverem verdes, banco estiver revisado e rollback estiver documentado. -``` - ---- - -## 11. Local recomendado no GitHub - -Caminho recomendado no repositório: - -```txt -docs/redeploy/AUDITORIA_REDEPLOY_PROMO_GIFTS_2026-05-13_15-32.md -``` - -Mensagem de commit: - -```txt -docs: add redeploy audit and hardening plan -``` - ---- - -## 12. Conclusão - -O projeto está próximo de estar pronto para redeploy, mas ainda precisa de hardening em pontos específicos. - -Prioridade absoluta: - -```txt -1. Proteger webhook-dispatcher -2. Proteger connections-auto-test -3. Fazer drift check antes de qualquer db push -``` - -Depois dessas correções, o projeto pode seguir para validação final e redeploy controlado. diff --git a/scripts/generate-theme-report.ts b/scripts/generate-theme-report.ts deleted file mode 100644 index ab3584546..000000000 --- a/scripts/generate-theme-report.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -const JSON_FILE = path.join(process.cwd(), 'playwright-report', 'theme-validation-data.json'); -const HTML_FILE = path.join(process.cwd(), 'playwright-report', 'theme-validation-report.html'); -const CSV_FILE = path.join(process.cwd(), 'playwright-report', 'theme-validation-report.csv'); - -interface Failure { - preset: string; - mode: string; - route: string; - type: string; - details: string; -} - -if (!fs.existsSync(JSON_FILE)) { - console.error('Nenhum dado de validação encontrado. Rode os testes primeiro.'); - process.exit(1); -} - -const failures: Failure[] = JSON.parse(fs.readFileSync(JSON_FILE, 'utf-8')); - -// Gerar CSV -const csvHeader = 'Preset,Mode,Route,Type,Details\n'; -const csvRows = failures.map(f => `"${f.preset}","${f.mode}","${f.route}","${f.type}","${f.details.replace(/"/g, '""')}"`).join('\n'); -fs.writeFileSync(CSV_FILE, csvHeader + csvRows); - -// Gerar HTML -const htmlContent = ` - - - - - Relatório de Validação de Temas - - - -

Relatório de Validação de Temas (SKINs)

-

Total de falhas encontradas: ${failures.length}

- - ${failures.length === 0 ? '

Nenhuma falha detectada! Todas as skins estão consistentes.

' : ` - - - - - - - - - - - - ${failures.map(f => ` - - - - - - - - `).join('')} - -
PresetModoRotaTipoDetalhes
${f.preset}${f.mode}${f.route}${f.type.toUpperCase()}${f.details}
- `} - - -`; - -fs.writeFileSync(HTML_FILE, htmlContent); - -console.log(`Relatórios gerados com sucesso:\n- ${HTML_FILE}\n- ${CSV_FILE}`); diff --git a/scripts/test-external-db.ts b/scripts/test-external-db.ts deleted file mode 100644 index d860390de..000000000 --- a/scripts/test-external-db.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import { createClient } from '@supabase/supabase-js'; - -const url = process.env.EXTERNAL_SUPABASE_URL; -const key = process.env.EXTERNAL_SUPABASE_SERVICE_ROLE_KEY; - -if (!url || !key) { - console.error('Missing EXTERNAL_SUPABASE_URL or EXTERNAL_SUPABASE_SERVICE_ROLE_KEY'); - process.exit(1); -} - -const supabase = createClient(url, key); - -async function test() { - const { data, error } = await supabase.from('profiles').select('*').limit(1); - if (error) { - console.log('Connection test failed or table profiles does not exist:', error.message); - } else { - console.log('Connection successful, found data in profiles.'); - } -} - -test(); diff --git a/src/contexts/AuthContext.test.tsx b/src/contexts/AuthContext.test.tsx index 2d99a086e..f90935ea9 100644 --- a/src/contexts/AuthContext.test.tsx +++ b/src/contexts/AuthContext.test.tsx @@ -2,8 +2,8 @@ import { renderHook, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { AuthProvider, useAuth } from './AuthContext'; import { supabase } from '@/integrations/supabase/client'; -import { type ReactNode } from 'react'; import type * as AuthServiceModule from '@/services/authService'; +import { type ReactNode } from 'react'; // Mock Supabase vi.mock('@/integrations/supabase/client', () => ({ diff --git a/src/lib/kit-og-image.ts b/src/lib/kit-og-image.ts deleted file mode 100644 index 60abb966a..000000000 --- a/src/lib/kit-og-image.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * kit-og-image — gera uma imagem OG (1200x630) em data URL via canvas - * para enriquecer o compartilhamento social do link público do kit. - * Sem dependências externas; apenas Canvas API. - */ - -interface KitOgInput { - kitName: string; - organization?: string | null; - itemsCount?: number; - color?: string; // hex -} - -/** - * Renderiza uma imagem OG e devolve um data URL PNG. - * Retorna null em ambientes sem suporte a canvas (SSR). - */ -export function generateKitOgImage(input: KitOgInput): string | null { - if (typeof document === "undefined") return null; - try { - const canvas = document.createElement("canvas"); - canvas.width = 1200; - canvas.height = 630; - const ctx = canvas.getContext("2d"); - if (!ctx) return null; - - const color = input.color && /^#[0-9a-f]{6}$/i.test(input.color) ? input.color : "#3B82F6"; - // Background gradient diagonal - const grad = ctx.createLinearGradient(0, 0, 1200, 630); - grad.addColorStop(0, color); - grad.addColorStop(1, shade(color, -30)); - ctx.fillStyle = grad; - ctx.fillRect(0, 0, 1200, 630); - - // Subtle dark overlay for text contrast - ctx.fillStyle = "rgba(0,0,0,0.25)"; - ctx.fillRect(0, 0, 1200, 630); - - // Top label — organization - ctx.fillStyle = "rgba(255,255,255,0.85)"; - ctx.font = "500 28px system-ui, -apple-system, 'Segoe UI', sans-serif"; - ctx.textBaseline = "top"; - const orgLabel = (input.organization || "Apresentação de Kit").toUpperCase(); - ctx.fillText(orgLabel.slice(0, 60), 80, 80); - - // Kit name — large - ctx.fillStyle = "#ffffff"; - ctx.font = "700 84px system-ui, -apple-system, 'Segoe UI', sans-serif"; - wrapText(ctx, input.kitName || "Kit personalizado", 80, 200, 1040, 96, 3); - - // Footer — items count - if (typeof input.itemsCount === "number" && input.itemsCount > 0) { - ctx.fillStyle = "rgba(255,255,255,0.9)"; - ctx.font = "500 32px system-ui, -apple-system, 'Segoe UI', sans-serif"; - const itemsLabel = `${input.itemsCount} ${input.itemsCount === 1 ? "item" : "itens"}`; - ctx.fillText(itemsLabel, 80, 520); - } - - // Brand corner - ctx.fillStyle = "rgba(255,255,255,0.7)"; - ctx.font = "500 22px system-ui, -apple-system, 'Segoe UI', sans-serif"; - ctx.textAlign = "right"; - ctx.fillText("Promo Gifts", 1120, 540); - ctx.textAlign = "left"; - - return canvas.toDataURL("image/png"); - } catch { - return null; - } -} - -function wrapText( - ctx: CanvasRenderingContext2D, - text: string, - x: number, - y: number, - maxWidth: number, - lineHeight: number, - maxLines: number, -): void { - const words = text.split(/\s+/); - let line = ""; - let lines = 0; - for (let n = 0; n < words.length; n++) { - const test = line ? `${line} ${words[n]}` : words[n]; - const w = ctx.measureText(test).width; - if (w > maxWidth && line) { - ctx.fillText(line, x, y + lines * lineHeight); - line = words[n]; - lines += 1; - if (lines >= maxLines - 1) { - // last line — truncate remaining words with ellipsis - const remaining = words.slice(n).join(" "); - let truncated = remaining; - while (ctx.measureText(truncated + "…").width > maxWidth && truncated.length > 0) { - truncated = truncated.slice(0, -1); - } - ctx.fillText(truncated + "…", x, y + lines * lineHeight); - return; - } - } else { - line = test; - } - } - if (line) ctx.fillText(line, x, y + lines * lineHeight); -} - -function shade(hex: string, percent: number): string { - const num = parseInt(hex.slice(1), 16); - const r = Math.max(0, Math.min(255, (num >> 16) + Math.round(255 * (percent / 100)))); - const g = Math.max(0, Math.min(255, ((num >> 8) & 0xff) + Math.round(255 * (percent / 100)))); - const b = Math.max(0, Math.min(255, (num & 0xff) + Math.round(255 * (percent / 100)))); - return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`; -} diff --git a/src/test/setup.ts b/src/test/setup.ts deleted file mode 100644 index fa2d09628..000000000 --- a/src/test/setup.ts +++ /dev/null @@ -1,15 +0,0 @@ -import "@testing-library/jest-dom"; - -Object.defineProperty(window, "matchMedia", { - writable: true, - value: (query: string) => ({ - matches: false, - media: query, - onchange: null, - addListener: () => {}, - removeListener: () => {}, - addEventListener: () => {}, - removeEventListener: () => {}, - dispatchEvent: () => {}, - }), -}); diff --git a/src/utils/kitPdfGenerator.ts b/src/utils/kitPdfGenerator.ts deleted file mode 100644 index ed4bbfe30..000000000 --- a/src/utils/kitPdfGenerator.ts +++ /dev/null @@ -1,443 +0,0 @@ -/** - * Kit PDF Generator - * Generates a professional PDF for an assembled kit with composition, - * price breakdown, and personalization details. - */ - -import jsPDF from 'jspdf'; -import autoTable from 'jspdf-autotable'; -import { - type KitState, - formatCurrency, - formatVolume, - formatDimensions, - generatePriceBreakdown, - calculateTotalKitPrice, -} from '@/lib/kit-builder'; - -interface KitPdfOptions { - kitState: KitState; - kitQuantity: number; - kitName: string; - orgName?: string; - orgLogoUrl?: string; -} - -// Brand colors -const PRIMARY = [30, 64, 175] as const; // blue-700 -const GRAY_800 = [31, 41, 55] as const; -const GRAY_500 = [107, 114, 128] as const; -const _GRAY_200 = [229, 231, 235] as const; -const WHITE = [255, 255, 255] as const; -const _GREEN = [16, 185, 129] as const; - -function hexToRgb(hex?: string): readonly [number, number, number] | null { - if (!hex) return null; - const m = hex.replace('#', '').match(/^([0-9a-f]{6})$/i); - if (!m) return null; - const n = parseInt(m[1], 16); - return [(n >> 16) & 255, (n >> 8) & 255, n & 255] as const; -} - -function drawHeader( - doc: jsPDF, - kitName: string, - y: number, - orgName?: string, - orgLogoUrl?: string, - identity?: { color?: string; tag?: string | null; icon?: string }, -): number { - const accent = hexToRgb(identity?.color) ?? PRIMARY; - - // Identity color stripe (top 2mm) — reflects kit identity color - doc.setFillColor(...accent); - doc.rect(0, 0, 210, 2, 'F'); - - // Main header bar - doc.setFillColor(...PRIMARY); - doc.rect(0, 2, 210, 34, 'F'); - - const textStartX = 14; - - doc.setTextColor(...WHITE); - doc.setFontSize(18); - doc.setFont('helvetica', 'bold'); - doc.text(kitName || 'Kit Personalizado', textStartX, 18); - - doc.setFontSize(9); - doc.setFont('helvetica', 'normal'); - const dateStr = new Date().toLocaleDateString('pt-BR', { - day: '2-digit', - month: 'long', - year: 'numeric', - }); - doc.text(`Gerado em ${dateStr}${orgName ? ` • ${orgName}` : ''}`, textStartX, 28); - - // Identity tag pill (right side, above the badge) - if (identity?.tag) { - doc.setFillColor(...accent); - const tagText = identity.tag.toUpperCase(); - doc.setFontSize(7); - const tagW = doc.getTextWidth(tagText) + 6; - doc.roundedRect(196 - tagW, 10, tagW, 6, 1.5, 1.5, 'F'); - doc.setTextColor(...WHITE); - doc.text(tagText, 196 - tagW / 2, 14.2, { align: 'center' }); - } - - // Right-aligned badge - doc.setTextColor(...WHITE); - doc.setFontSize(8); - doc.text('FICHA DO KIT', 196, 22, { align: 'right' }); - - return 44; -} - -function drawSectionTitle(doc: jsPDF, title: string, y: number): number { - doc.setFontSize(12); - doc.setFont('helvetica', 'bold'); - doc.setTextColor(...GRAY_800); - doc.text(title, 14, y); - - // Underline - doc.setDrawColor(...PRIMARY); - doc.setLineWidth(0.6); - doc.line(14, y + 2, 60, y + 2); - - return y + 10; -} - -function drawKpiCards(doc: jsPDF, kitState: KitState, kitQuantity: number, y: number): number { - const { items, personalization, totalWeight, box } = kitState; - const totalItems = items.reduce((s, i) => s + i.quantity, 0); - const personalizedCount = - (personalization.box.enabled ? 1 : 0) + - Object.values(personalization.items).filter((p) => p.enabled).length; - - const cards = [ - { label: 'Embalagem', value: '1', sub: box?.name || '-' }, - { label: 'Itens', value: String(totalItems), sub: `${items.length} diferentes` }, - { - label: 'Personalizações', - value: String(personalizedCount), - sub: personalizedCount === 1 ? 'item' : 'itens', - }, - { - label: 'Peso estimado', - value: totalWeight >= 1000 ? `${(totalWeight / 1000).toFixed(1)}kg` : `${totalWeight}g`, - sub: `x${kitQuantity} kits`, - }, - ]; - - const cardW = 43; - const gap = 4; - const startX = 14; - - cards.forEach((card, i) => { - const x = startX + i * (cardW + gap); - // Card bg - doc.setFillColor(245, 247, 250); - doc.roundedRect(x, y, cardW, 26, 2, 2, 'F'); - - // Value - doc.setFontSize(16); - doc.setFont('helvetica', 'bold'); - doc.setTextColor(...GRAY_800); - doc.text(card.value, x + cardW / 2, y + 12, { align: 'center' }); - - // Label - doc.setFontSize(7); - doc.setFont('helvetica', 'normal'); - doc.setTextColor(...GRAY_500); - doc.text(card.label, x + cardW / 2, y + 18, { align: 'center' }); - - // Sub - doc.setFontSize(6); - doc.text(card.sub, x + cardW / 2, y + 23, { align: 'center' }); - }); - - return y + 34; -} - -function drawCompositionTable(doc: jsPDF, kitState: KitState, y: number): number { - const { box, items, personalization } = kitState; - - const rows: (string | number)[][] = []; - - // Box row - if (box) { - rows.push([ - '📦 ' + box.name, - box.sku || '-', - box.material || '-', - '1', - formatCurrency(box.price), - personalization.box.enabled ? personalization.box.techniqueName || 'Sim' : '-', - ]); - } - - // Item rows - items.forEach((item) => { - const itemP = personalization.items[item.id]; - rows.push([ - '🎁 ' + item.name + (item.isOptional ? ' (Opcional)' : ''), - item.sku || '-', - item.material || '-', - String(item.quantity), - formatCurrency(item.price), - itemP?.enabled ? itemP.techniqueName || 'Sim' : '-', - ]); - }); - - autoTable(doc, { - startY: y, - head: [['Item', 'SKU', 'Material', 'Qtd', 'Preço Unit.', 'Personalização']], - body: rows, - theme: 'striped', - headStyles: { - fillColor: PRIMARY as unknown as number[], - textColor: WHITE as unknown as number[], - fontSize: 8, - fontStyle: 'bold', - }, - bodyStyles: { - fontSize: 7.5, - textColor: GRAY_800 as unknown as number[], - }, - alternateRowStyles: { - fillColor: [248, 250, 252], - }, - columnStyles: { - 0: { cellWidth: 55 }, - 3: { halign: 'center', cellWidth: 14 }, - 4: { halign: 'right', cellWidth: 24 }, - 5: { cellWidth: 30 }, - }, - margin: { left: 14, right: 14 }, - }); - - return doc.lastAutoTable.finalY + 8; -} - -function drawPersonalizationDetails(doc: jsPDF, kitState: KitState, y: number): number { - const { personalization, box, items } = kitState; - - const entries: { - name: string; - technique: string; - colors: string; - dimensions: string; - price: string; - }[] = []; - - if (personalization.box.enabled && box) { - entries.push({ - name: `Caixa: ${box.name}`, - technique: personalization.box.techniqueName || '-', - colors: personalization.box.colors ? `${personalization.box.colors} cor(es)` : '-', - dimensions: - personalization.box.width && personalization.box.height - ? `${personalization.box.width}x${personalization.box.height}cm` - : '-', - price: personalization.box.estimatedPrice - ? formatCurrency(personalization.box.estimatedPrice) - : '-', - }); - } - - items.forEach((item) => { - const p = personalization.items[item.id]; - if (p?.enabled) { - entries.push({ - name: item.name, - technique: p.techniqueName || '-', - colors: p.colors ? `${p.colors} cor(es)` : '-', - dimensions: p.width && p.height ? `${p.width}x${p.height}cm` : '-', - price: p.estimatedPrice ? formatCurrency(p.estimatedPrice) + '/un' : '-', - }); - } - }); - - if (entries.length === 0) return y; - - y = drawSectionTitle(doc, 'Detalhes de Personalização', y); - - autoTable(doc, { - startY: y, - head: [['Item', 'Técnica', 'Cores', 'Área', 'Custo/un']], - body: entries.map((e) => [e.name, e.technique, e.colors, e.dimensions, e.price]), - theme: 'grid', - headStyles: { - fillColor: [99, 102, 241] as number[], - textColor: WHITE as unknown as number[], - fontSize: 8, - fontStyle: 'bold', - }, - bodyStyles: { - fontSize: 7.5, - textColor: GRAY_800 as unknown as number[], - }, - margin: { left: 14, right: 14 }, - }); - - return doc.lastAutoTable.finalY + 8; -} - -function drawPriceBreakdown( - doc: jsPDF, - kitState: KitState, - kitQuantity: number, - y: number, -): number { - const { box, items, personalization } = kitState; - const breakdown = generatePriceBreakdown(box, items, personalization, kitQuantity); - const pricing = calculateTotalKitPrice(box, items, personalization, kitQuantity); - - y = drawSectionTitle(doc, 'Detalhamento de Preços', y); - - const rows = breakdown.map((item) => [ - item.isPersonalization ? ` ↳ ${item.label}` : item.label, - item.quantity ? String(item.quantity) : '-', - formatCurrency(item.unitPrice), - formatCurrency(item.totalPrice), - ]); - - autoTable(doc, { - startY: y, - head: [['Descrição', 'Qtd', 'Preço Unit.', 'Subtotal']], - body: rows, - theme: 'striped', - headStyles: { - fillColor: GRAY_800 as unknown as number[], - textColor: WHITE as unknown as number[], - fontSize: 8, - fontStyle: 'bold', - }, - bodyStyles: { - fontSize: 7.5, - textColor: GRAY_800 as unknown as number[], - }, - columnStyles: { - 1: { halign: 'center', cellWidth: 18 }, - 2: { halign: 'right', cellWidth: 26 }, - 3: { halign: 'right', cellWidth: 26 }, - }, - margin: { left: 14, right: 14 }, - didParseCell: (data: { - cell: { raw: unknown; styles: Record }; - column: { index: number }; - row: { index: number }; - section: string; - }) => { - // Indent personalization rows - if (data.section === 'body') { - const text = String(data.cell.raw); - if (text.startsWith(' ↳')) { - data.cell.styles.textColor = [99, 102, 241]; - data.cell.styles.fontStyle = 'italic'; - } - } - }, - }); - - const finalTableY = doc.lastAutoTable.finalY; - - // Total box - const totalBoxY = finalTableY + 4; - doc.setFillColor(245, 247, 250); - doc.roundedRect(110, totalBoxY, 86, 24, 2, 2, 'F'); - - doc.setFontSize(8); - doc.setFont('helvetica', 'normal'); - doc.setTextColor(...GRAY_500); - doc.text(`Subtotal (${kitQuantity} kit${kitQuantity > 1 ? 's' : ''})`, 114, totalBoxY + 7); - doc.text(formatCurrency(pricing.subtotal), 192, totalBoxY + 7, { align: 'right' }); - - if (pricing.personalizationPrice > 0) { - doc.text('Personalização', 114, totalBoxY + 13); - doc.setTextColor(99, 102, 241); - doc.text(formatCurrency(pricing.personalizationPrice), 192, totalBoxY + 13, { align: 'right' }); - } - - doc.setFontSize(11); - doc.setFont('helvetica', 'bold'); - doc.setTextColor(...PRIMARY); - doc.text('TOTAL', 114, totalBoxY + 21); - doc.text(formatCurrency(pricing.total), 192, totalBoxY + 21, { align: 'right' }); - - // Unit price - doc.setFontSize(7); - doc.setFont('helvetica', 'normal'); - doc.setTextColor(...GRAY_500); - doc.text(`(${formatCurrency(pricing.unitPrice)}/kit)`, 140, totalBoxY + 21); - - return totalBoxY + 30; -} - -function drawFooter(doc: jsPDF) { - const pageHeight = doc.internal.pageSize.getHeight(); - doc.setFontSize(7); - doc.setFont('helvetica', 'normal'); - doc.setTextColor(...GRAY_500); - doc.text( - 'Valores estimados, sujeitos a confirmação. Preços de personalização podem variar conforme arte final.', - 14, - pageHeight - 10, - ); - doc.text(`Página 1`, 196, pageHeight - 10, { align: 'right' }); -} - -export function generateKitPDF(options: KitPdfOptions): Blob { - const { kitState, kitQuantity, kitName } = options; - const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); - - let y = drawHeader(doc, kitName, 0, options.orgName, options.orgLogoUrl, kitState.identity); - - // KPI cards - y = drawKpiCards(doc, kitState, kitQuantity, y); - - // Composition section - y = drawSectionTitle(doc, 'Composição do Kit', y); - y = drawCompositionTable(doc, kitState, y); - - // Box info line - if (kitState.box) { - doc.setFontSize(7); - doc.setFont('helvetica', 'normal'); - doc.setTextColor(...GRAY_500); - doc.text( - `Caixa: ${formatDimensions(kitState.box.internalWidth, kitState.box.internalHeight, kitState.box.internalDepth)} | Volume: ${formatVolume(kitState.box.internalVolume)} | Ocupação: ${kitState.volumeUsagePercent.toFixed(0)}%`, - 14, - y, - ); - y += 8; - } - - // Personalization details - y = drawPersonalizationDetails(doc, kitState, y); - - // Check if we need a new page for price breakdown - if (y > 210) { - doc.addPage(); - y = 20; - } - - // Price breakdown - y = drawPriceBreakdown(doc, kitState, kitQuantity, y); - - // Footer - drawFooter(doc); - - return doc.output('blob'); -} - -export function downloadKitPDF(options: KitPdfOptions): void { - const blob = generateKitPDF(options); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `kit-${(options.kitName || 'personalizado').replace(/\s+/g, '-').toLowerCase()}.pdf`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} diff --git a/tests/e2e/admin-module.test.ts b/tests/e2e/admin-module.test.ts deleted file mode 100644 index ccebd9dad..000000000 --- a/tests/e2e/admin-module.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * E2E Tests — Admin Module - * Covers: Users, Roles, Permissions, Security, Product Registration, Telemetry - */ -import { describe, it, expect } from 'vitest'; - -// ============ User Management ============ -interface AdminUser { - id: string; - email: string; - full_name: string | null; - role: 'admin' | 'manager' | 'vendedor'; - is_active: boolean; - last_login_at: string | null; - department: string | null; -} - -const sampleUsers: AdminUser[] = [ - { id: '1', email: 'admin@test.com', full_name: 'Admin User', role: 'admin', is_active: true, last_login_at: '2025-03-20', department: 'TI' }, - { id: '2', email: 'manager@test.com', full_name: 'Manager User', role: 'manager', is_active: true, last_login_at: '2025-03-19', department: 'Vendas' }, - { id: '3', email: 'seller@test.com', full_name: 'Seller User', role: 'vendedor', is_active: true, last_login_at: '2025-03-18', department: 'Vendas' }, - { id: '4', email: 'inactive@test.com', full_name: 'Inactive', role: 'vendedor', is_active: false, last_login_at: null, department: null }, -]; - -describe('E2E Admin — User Management', () => { - it('lists all users', () => expect(sampleUsers).toHaveLength(4)); - it('filters active users', () => expect(sampleUsers.filter(u => u.is_active)).toHaveLength(3)); - it('filters inactive users', () => expect(sampleUsers.filter(u => !u.is_active)).toHaveLength(1)); - it('filters by role', () => expect(sampleUsers.filter(u => u.role === 'vendedor')).toHaveLength(2)); - it('filters by department', () => expect(sampleUsers.filter(u => u.department === 'Vendas')).toHaveLength(2)); - it('search by name', () => expect(sampleUsers.filter(u => u.full_name?.toLowerCase().includes('admin'))).toHaveLength(1)); - it('search by email', () => expect(sampleUsers.filter(u => u.email.includes('seller'))).toHaveLength(1)); - it('users with no login', () => expect(sampleUsers.filter(u => !u.last_login_at)).toHaveLength(1)); - - it('activate user', () => { - const user = { ...sampleUsers[3], is_active: true }; - expect(user.is_active).toBe(true); - }); - - it('deactivate user', () => { - const user = { ...sampleUsers[0], is_active: false }; - expect(user.is_active).toBe(false); - }); - - it('change role', () => { - const user = { ...sampleUsers[2], role: 'manager' as const }; - expect(user.role).toBe('manager'); - }); -}); - -// ============ Role-Based Access Control ============ -describe('E2E Admin — RBAC', () => { - const permissions = { - admin: ['users.read', 'users.write', 'users.delete', 'roles.manage', 'settings.write', 'products.write', 'quotes.all', 'analytics.read'], - manager: ['users.read', 'products.write', 'quotes.all', 'analytics.read'], - vendedor: ['products.read', 'quotes.own', 'carts.own'], - }; - - it('admin has most permissions', () => expect(permissions.admin.length).toBeGreaterThan(permissions.manager.length)); - it('manager has more than vendedor', () => expect(permissions.manager.length).toBeGreaterThan(permissions.vendedor.length)); - it('vendedor can read products', () => expect(permissions.vendedor).toContain('products.read')); - it('vendedor cannot write products', () => expect(permissions.vendedor).not.toContain('products.write')); - it('admin can delete users', () => expect(permissions.admin).toContain('users.delete')); - it('vendedor cannot delete users', () => expect(permissions.vendedor).not.toContain('users.delete')); - - function hasPermission(role: keyof typeof permissions, perm: string): boolean { - return permissions[role].includes(perm); - } - - it('admin has users.write', () => expect(hasPermission('admin', 'users.write')).toBe(true)); - it('vendedor lacks users.write', () => expect(hasPermission('vendedor', 'users.write')).toBe(false)); - it('manager can write products', () => expect(hasPermission('manager', 'products.write')).toBe(true)); -}); - -// ============ Product Registration ============ -describe('E2E Admin — Product Registration', () => { - const productForm = { - nome: 'Caneta Nova', codigo: 'CAN-999', preco: 5.50, - categoria_id: 'cat-1', fornecedor: 'BIC', - descricao: 'Caneta esferográfica personalizada', - peso: 15, largura: 1.5, altura: 14, profundidade: 1.5, - cores: ['Azul', 'Preto', 'Vermelho'], - imagens: ['img1.jpg', 'img2.jpg'], - ativo: true, - }; - - it('has required fields', () => { - expect(productForm.nome).toBeTruthy(); - expect(productForm.codigo).toBeTruthy(); - expect(productForm.preco).toBeGreaterThan(0); - }); - it('has colors array', () => expect(productForm.cores).toHaveLength(3)); - it('has images array', () => expect(productForm.imagens).toHaveLength(2)); - it('has dimensions', () => { - expect(productForm.peso).toBeGreaterThan(0); - expect(productForm.largura).toBeGreaterThan(0); - expect(productForm.altura).toBeGreaterThan(0); - }); - it('SKU format is valid', () => expect(productForm.codigo).toMatch(/^[A-Z]+-\d+$/)); - it('active by default', () => expect(productForm.ativo).toBe(true)); -}); - -// ============ Security Settings ============ -describe('E2E Admin — Security', () => { - const securitySettings = { - maxLoginAttempts: 5, - lockoutDuration: 30, // minutes - passwordMinLength: 8, - requireUppercase: true, - requireLowercase: true, - requireNumber: true, - requireSpecialChar: true, - sessionTimeout: 480, // minutes (8 hours) - ipWhitelist: ['192.168.1.0/24', '10.0.0.0/8'], - enable2FA: false, - }; - - it('has max login attempts', () => expect(securitySettings.maxLoginAttempts).toBeGreaterThan(0)); - it('lockout duration > 0', () => expect(securitySettings.lockoutDuration).toBeGreaterThan(0)); - it('password min length >= 8', () => expect(securitySettings.passwordMinLength).toBeGreaterThanOrEqual(8)); - it('requires all char types', () => { - expect(securitySettings.requireUppercase).toBe(true); - expect(securitySettings.requireLowercase).toBe(true); - expect(securitySettings.requireNumber).toBe(true); - expect(securitySettings.requireSpecialChar).toBe(true); - }); - it('session timeout is 8 hours', () => expect(securitySettings.sessionTimeout).toBe(480)); - it('IP whitelist has entries', () => expect(securitySettings.ipWhitelist.length).toBeGreaterThan(0)); - it('CIDR notation valid', () => { - securitySettings.ipWhitelist.forEach(ip => expect(ip).toMatch(/\d+\.\d+\.\d+\.\d+\/\d+/)); - }); -}); - -// ============ Telemetry ============ -describe('E2E Admin — Telemetry', () => { - const telemetryEntry = { - id: 'tel-1', operation: 'select', table_name: 'products', - duration_ms: 45, severity: 'info', record_count: 150, - query_limit: 1000, query_offset: 0, error_message: null, - }; - - it('tracks operation type', () => expect(telemetryEntry.operation).toBe('select')); - it('has duration', () => expect(telemetryEntry.duration_ms).toBeGreaterThan(0)); - it('has severity', () => expect(['info', 'warn', 'error']).toContain(telemetryEntry.severity)); - it('tracks record count', () => expect(telemetryEntry.record_count).toBeGreaterThanOrEqual(0)); - it('no error by default', () => expect(telemetryEntry.error_message).toBeNull()); - - it('slow query detection', () => { - const SLOW_THRESHOLD = 1000; - expect(telemetryEntry.duration_ms < SLOW_THRESHOLD).toBe(true); - const slow = { ...telemetryEntry, duration_ms: 2500 }; - expect(slow.duration_ms >= SLOW_THRESHOLD).toBe(true); - }); -}); diff --git a/tests/e2e/analytics-bi.test.ts b/tests/e2e/analytics-bi.test.ts deleted file mode 100644 index d8961e166..000000000 --- a/tests/e2e/analytics-bi.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * E2E Tests — Analytics & BI Module - * Covers: Metrics, Charts, Filters, Export - */ -import { describe, it, expect } from 'vitest'; - -// ============ BI Metrics ============ -describe('E2E Analytics — BI Metrics', () => { - const metrics = { - totalQuotes: 150, approvedQuotes: 85, rejectedQuotes: 25, pendingQuotes: 40, - totalRevenue: 450000, avgTicket: 5294.12, - conversionRate: 56.67, // approved / total * 100 - topProducts: ['Caneta', 'Caderno', 'Squeeze'], - topClients: ['Alpha SA', 'Beta Corp', 'Gamma LTDA'], - quotesThisMonth: 32, quotesLastMonth: 28, - }; - - it('total = approved + rejected + pending', () => { - expect(metrics.approvedQuotes + metrics.rejectedQuotes + metrics.pendingQuotes).toBe(metrics.totalQuotes); - }); - it('conversion rate is correct', () => { - const calc = (metrics.approvedQuotes / metrics.totalQuotes * 100).toFixed(2); - expect(parseFloat(calc)).toBeCloseTo(metrics.conversionRate, 1); - }); - it('avg ticket = revenue / approved', () => { - const calc = metrics.totalRevenue / metrics.approvedQuotes; - expect(calc).toBeCloseTo(metrics.avgTicket, 0); - }); - it('has top products', () => expect(metrics.topProducts.length).toBeGreaterThanOrEqual(3)); - it('has top clients', () => expect(metrics.topClients.length).toBeGreaterThanOrEqual(3)); - it('month-over-month growth', () => { - const growth = ((metrics.quotesThisMonth - metrics.quotesLastMonth) / metrics.quotesLastMonth * 100); - expect(growth).toBeCloseTo(14.29, 1); - }); -}); - -// ============ Chart Data ============ -describe('E2E Analytics — Chart Data', () => { - const monthlyData = [ - { month: 'Jan', quotes: 20, revenue: 45000 }, - { month: 'Fev', quotes: 18, revenue: 38000 }, - { month: 'Mar', quotes: 25, revenue: 62000 }, - { month: 'Abr', quotes: 30, revenue: 78000 }, - { month: 'Mai', quotes: 22, revenue: 51000 }, - { month: 'Jun', quotes: 35, revenue: 95000 }, - ]; - - it('has 6 months of data', () => expect(monthlyData).toHaveLength(6)); - it('data has month label', () => monthlyData.forEach(d => expect(d.month).toBeTruthy())); - it('data has quotes count', () => monthlyData.forEach(d => expect(d.quotes).toBeGreaterThan(0))); - it('data has revenue', () => monthlyData.forEach(d => expect(d.revenue).toBeGreaterThan(0))); - - it('total revenue across months', () => { - const total = monthlyData.reduce((s, d) => s + d.revenue, 0); - expect(total).toBe(369000); - }); - - it('best month by revenue', () => { - const best = monthlyData.reduce((max, d) => d.revenue > max.revenue ? d : max); - expect(best.month).toBe('Jun'); - }); - - it('best month by quotes', () => { - const best = monthlyData.reduce((max, d) => d.quotes > max.quotes ? d : max); - expect(best.month).toBe('Jun'); - }); -}); - -// ============ Date Range Filters ============ -describe('E2E Analytics — Date Filters', () => { - const ranges = ['today', '7d', '30d', '90d', 'year', 'custom'] as const; - - it('has 6 range options', () => expect(ranges).toHaveLength(6)); - it('includes today', () => expect(ranges).toContain('today')); - it('includes custom', () => expect(ranges).toContain('custom')); - - function getRangeDates(range: string): { start: Date; end: Date } { - const end = new Date(); - const start = new Date(); - switch (range) { - case 'today': break; - case '7d': start.setDate(start.getDate() - 7); break; - case '30d': start.setDate(start.getDate() - 30); break; - case '90d': start.setDate(start.getDate() - 90); break; - case 'year': start.setFullYear(start.getFullYear() - 1); break; - } - return { start, end }; - } - - it('7d range is 7 days back', () => { - const { start, end } = getRangeDates('7d'); - const diff = Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); - expect(diff).toBe(7); - }); - - it('30d range is 30 days', () => { - const { start, end } = getRangeDates('30d'); - const diff = Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); - expect(diff).toBe(30); - }); -}); - -// ============ Export ============ -describe('E2E Analytics — Export', () => { - const exportFormats = ['csv', 'xlsx', 'pdf'] as const; - - it('supports CSV', () => expect(exportFormats).toContain('csv')); - it('supports XLSX', () => expect(exportFormats).toContain('xlsx')); - it('supports PDF', () => expect(exportFormats).toContain('pdf')); - it('has 3 formats', () => expect(exportFormats).toHaveLength(3)); -}); - -// ============ Trends ============ -describe('E2E Analytics — Trends', () => { - const trendCategories = ['Mais Vendidos', 'Mais Consultados', 'Maior Crescimento', 'Produtos Novos']; - - it('has trend categories', () => expect(trendCategories.length).toBeGreaterThanOrEqual(4)); - trendCategories.forEach(cat => { - it(`category "${cat}" exists`, () => expect(cat).toBeTruthy()); - }); -}); diff --git a/tests/e2e/artifacts/compare/viewer/audit-snapshot.html b/tests/e2e/artifacts/compare/viewer/audit-snapshot.html deleted file mode 100644 index 4b14c29e6..000000000 --- a/tests/e2e/artifacts/compare/viewer/audit-snapshot.html +++ /dev/null @@ -1 +0,0 @@ -

Comparador de Produtos

Comparando 2 produtos

Foto
Prod 1
Prod 2
Produto
Preço
R$ 10,00
R$ 20,00
Qtd. mínima
1 un.
2 un.
Estoque
10
20
Cores
Fornecedor
S1
S1
\ No newline at end of file diff --git a/tests/e2e/artifacts/compare/viewer/initial-comparison-view-styles.json b/tests/e2e/artifacts/compare/viewer/initial-comparison-view-styles.json deleted file mode 100644 index f535c1294..000000000 --- a/tests/e2e/artifacts/compare/viewer/initial-comparison-view-styles.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "div": { - "display": "block", - "position": "", - "color": "" - }, - "div.sr-only": { - "display": "block", - "position": "", - "color": "" - }, - "div.w-full.max-w-[1920px].mx-auto.px-3.sm:px-4.lg:px-6.xl:px-8.py-3.sm:py-4.space-y-3.sm:space-y-4.pb-24.md:pb-6.animate-fade-in": { - "display": "block", - "position": "", - "color": "" - }, - "div.flex.flex-col.sm:flex-row.sm:items-center.sm:justify-between.gap-4": { - "display": "block", - "position": "", - "color": "" - }, - "div.flex.items-center.gap-3": { - "display": "block", - "position": "", - "color": "" - }, - "button.inline-flex.items-center.justify-center.gap-2.whitespace-nowrap.rounded-lg.text-sm.ring-offset-background.transition-standard.focus-visible:outline-none.focus-visible:ring-2.focus-visible:ring-primary.focus-visible:ring-offset-2.disabled:pointer-events-none.disabled:opacity-50.[&_svg]:pointer-events-none.[&_svg]:size-4.[&_svg]:shrink-0.touch-manipulation.active:scale-[0.96].hover:-translate-y-0.5.hover:bg-primary/10.hover:text-primary.font-bold.active:bg-primary/20.h-11.w-11.min-h-[44px].min-w-[44px]": { - "display": "inline-block", - "position": "", - "color": "ButtonText" - }, - "h1.text-2xl.lg:text-3xl.font-display.font-bold.text-foreground": { - "display": "block", - "position": "", - "color": "" - }, - "p.text-muted-foreground": { - "display": "block", - "position": "", - "color": "" - }, - "div.flex.flex-wrap.gap-2": { - "display": "block", - "position": "", - "color": "" - }, - "button.inline-flex.items-center.justify-center.gap-2.whitespace-nowrap.text-sm.font-bold.ring-offset-background.transition-standard.focus-visible:outline-none.focus-visible:ring-2.focus-visible:ring-primary.focus-visible:ring-offset-2.disabled:pointer-events-none.disabled:opacity-50.[&_svg]:pointer-events-none.[&_svg]:size-4.[&_svg]:shrink-0.touch-manipulation.active:scale-[0.96].hover:-translate-y-0.5.border-2.border-primary/30.bg-background.text-primary.hover:border-primary.hover:bg-primary/5.active:bg-primary/10.active:shadow-inner.h-9.rounded-lg.px-3.min-h-[36px]": { - "display": "inline-block", - "position": "", - "color": "ButtonText" - }, - "button.inline-flex.items-center.justify-center.gap-2.whitespace-nowrap.text-sm.font-bold.ring-offset-background.transition-standard.focus-visible:outline-none.focus-visible:ring-2.focus-visible:ring-primary.focus-visible:ring-offset-2.disabled:pointer-events-none.disabled:opacity-50.[&_svg]:pointer-events-none.[&_svg]:size-4.[&_svg]:shrink-0.touch-manipulation.active:scale-[0.96].hover:-translate-y-0.5.bg-primary.text-primary-foreground.hover:bg-primary-hover.active:bg-primary-active.shadow-sm.active:shadow-inner.border.border-primary/20.h-9.rounded-lg.px-3.min-h-[36px]": { - "display": "inline-block", - "position": "", - "color": "ButtonText" - }, - "span": { - "display": "", - "position": "", - "color": "" - } -} \ No newline at end of file diff --git a/tests/e2e/artifacts/compare/viewer/initial-comparison-view.html b/tests/e2e/artifacts/compare/viewer/initial-comparison-view.html deleted file mode 100644 index 62c09abd9..000000000 --- a/tests/e2e/artifacts/compare/viewer/initial-comparison-view.html +++ /dev/null @@ -1 +0,0 @@ -

Comparador de Produtos

Comparando 2 produtos

Foto
Prod 1
Prod 2
Produto
Preço
R$ 10,00
R$ 20,00
Qtd. mínima
1 un.
2 un.
Estoque
10
20
Cores
Fornecedor
S1
S1
\ No newline at end of file diff --git a/tests/e2e/artifacts/compare/viewer/initial-view.html b/tests/e2e/artifacts/compare/viewer/initial-view.html deleted file mode 100644 index 32c08e039..000000000 --- a/tests/e2e/artifacts/compare/viewer/initial-view.html +++ /dev/null @@ -1 +0,0 @@ -

Comparador de Produtos

Comparando 2 produtos

Foto
Prod 1
Prod 2
Produto
Preço
R$ 10,00
R$ 20,00
Qtd. mínima
1 un.
2 un.
Estoque
10
20
Cores
Fornecedor
S1
S1
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/audit-report/builder-full.html b/tests/e2e/artifacts/quotes/audit-report/builder-full.html deleted file mode 100644 index 12061e7ec..000000000 --- a/tests/e2e/artifacts/quotes/audit-report/builder-full.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Busque produtos na coluna ao lado para começar

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/ci-final-resilient/builder-snapshot.html b/tests/e2e/artifacts/quotes/ci-final-resilient/builder-snapshot.html deleted file mode 100644 index 12061e7ec..000000000 --- a/tests/e2e/artifacts/quotes/ci-final-resilient/builder-snapshot.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Busque produtos na coluna ao lado para começar

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/ci-final/step-items.html b/tests/e2e/artifacts/quotes/ci-final/step-items.html deleted file mode 100644 index b38e90903..000000000 --- a/tests/e2e/artifacts/quotes/ci-final/step-items.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/ci-report/identificacao/current.html b/tests/e2e/artifacts/quotes/ci-report/identificacao/current.html deleted file mode 100644 index 5810efcbb..000000000 --- a/tests/e2e/artifacts/quotes/ci-report/identificacao/current.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/ci-report/itens/current.html b/tests/e2e/artifacts/quotes/ci-report/itens/current.html deleted file mode 100644 index 5810efcbb..000000000 --- a/tests/e2e/artifacts/quotes/ci-report/itens/current.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/ci-visual/builder-layout.html b/tests/e2e/artifacts/quotes/ci-visual/builder-layout.html deleted file mode 100644 index 5810efcbb..000000000 --- a/tests/e2e/artifacts/quotes/ci-visual/builder-layout.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/full-cycle/audit-snapshot-conditions.html b/tests/e2e/artifacts/quotes/full-cycle/audit-snapshot-conditions.html deleted file mode 100644 index 5810efcbb..000000000 --- a/tests/e2e/artifacts/quotes/full-cycle/audit-snapshot-conditions.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/full-cycle/resilience-layout-stability.html b/tests/e2e/artifacts/quotes/full-cycle/resilience-layout-stability.html deleted file mode 100644 index b38e90903..000000000 --- a/tests/e2e/artifacts/quotes/full-cycle/resilience-layout-stability.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/visual/quote-builder-initial.html b/tests/e2e/artifacts/quotes/visual/quote-builder-initial.html deleted file mode 100644 index 12061e7ec..000000000 --- a/tests/e2e/artifacts/quotes/visual/quote-builder-initial.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Busque produtos na coluna ao lado para começar

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/artifacts/quotes/visual/step-identification.html b/tests/e2e/artifacts/quotes/visual/step-identification.html deleted file mode 100644 index 085e8d3c2..000000000 --- a/tests/e2e/artifacts/quotes/visual/step-identification.html +++ /dev/null @@ -1 +0,0 @@ -

Novo Orçamento

Crie um orçamento com produtos e personalizações

Cliente
Itens
Condições
Revisão
Selecione uma empresa primeiro

Selecione uma empresa

📅Validade | Proposta

Condições

Itens do Orçamento

0 item(ns) adicionado(s)

Nenhum item adicionado

Pesquise e adicione produtos ao orçamento

Resumo

Nenhum item adicionado

Total
R$ 0,00

Campos obrigatórios pendentes:

  • Empresa
  • Contato
  • Prazo de Pagamento
  • Prazo de Entrega
  • Frete
  • Itens do Orçamento
\ No newline at end of file diff --git a/tests/e2e/auth-flow.test.tsx b/tests/e2e/auth-flow.test.tsx deleted file mode 100644 index 038ae5ad8..000000000 --- a/tests/e2e/auth-flow.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/** - * E2E Tests — Auth Module - * Covers: Login, Signup, Logout, Protected Routes, Password Reset, IP Blocking - */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { renderWithProviders } from '../components/render-helpers'; - -// Mock modules before imports -const mockNavigate = vi.fn(); -const mockSignIn = vi.fn().mockResolvedValue({ error: null }); -const mockSignUp = vi.fn().mockResolvedValue({ error: null }); -const mockSignOut = vi.fn().mockResolvedValue(undefined); - -vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { ...actual, useNavigate: () => mockNavigate }; -}); - -vi.mock('@/contexts/AuthContext', () => ({ - useAuth: vi.fn().mockReturnValue({ - user: null, - session: null, - profile: null, - isLoading: false, - role: null, - isAdmin: false, - isManager: false, - isSeller: false, - canManage: false, - isAuthenticated: false, - signIn: mockSignIn, - signUp: mockSignUp, - signOut: mockSignOut, - refreshProfile: vi.fn(), - }), - AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}, -})); - -vi.mock('@/hooks/admin/useIPValidation', () => ({ - useIPValidation: () => ({ - validateIPForAuthenticatedUser: vi.fn().mockResolvedValue(true), - logLoginAttempt: vi.fn(), - fetchCurrentIP: vi.fn().mockResolvedValue('127.0.0.1'), - }), -})); - -// ============ Validation Schema Tests ============ -import { z } from 'zod'; - -const loginSchema = z.object({ - email: z.string().min(1, "Email é obrigatório").email("Email inválido"), - password: z.string().min(6, "Senha deve ter pelo menos 6 caracteres"), -}); - -const signupSchema = z.object({ - fullName: z.string().min(2, "Nome deve ter pelo menos 2 caracteres"), - email: z.string().email("Email inválido"), - password: z.string() - .min(8, "Senha deve ter pelo menos 8 caracteres") - .regex(/[A-Z]/, "Senha deve conter letra maiúscula") - .regex(/[a-z]/, "Senha deve conter letra minúscula") - .regex(/[0-9]/, "Senha deve conter número") - .regex(/[!@#$%^&*(),.?":{}|<>]/, "Senha deve conter caractere especial"), - confirmPassword: z.string(), -}).refine((data) => data.password === data.confirmPassword, { - message: "Senhas não conferem", - path: ["confirmPassword"], -}); - -describe('E2E Auth — Login Validation', () => { - it('rejects empty email', () => { - expect(loginSchema.safeParse({ email: '', password: '123456' }).success).toBe(false); - }); - it('rejects invalid email format', () => { - expect(loginSchema.safeParse({ email: 'abc', password: '123456' }).success).toBe(false); - }); - it('rejects email without domain', () => { - expect(loginSchema.safeParse({ email: 'user@', password: '123456' }).success).toBe(false); - }); - it('rejects password < 6 chars', () => { - expect(loginSchema.safeParse({ email: 'a@b.com', password: '12345' }).success).toBe(false); - }); - it('accepts valid login', () => { - expect(loginSchema.safeParse({ email: 'user@test.com', password: '123456' }).success).toBe(true); - }); - it('accepts email with subdomain', () => { - expect(loginSchema.safeParse({ email: 'user@mail.test.com', password: '123456' }).success).toBe(true); - }); - it('accepts long password', () => { - expect(loginSchema.safeParse({ email: 'u@t.com', password: 'a'.repeat(100) }).success).toBe(true); - }); - it('rejects email with spaces', () => { - expect(loginSchema.safeParse({ email: 'user @test.com', password: '123456' }).success).toBe(false); - }); -}); - -describe('E2E Auth — Signup Validation', () => { - const valid = { fullName: 'João Silva', email: 'joao@test.com', password: 'Secure1!x', confirmPassword: 'Secure1!x' }; - - it('accepts valid signup', () => expect(signupSchema.safeParse(valid).success).toBe(true)); - it('rejects name < 2 chars', () => expect(signupSchema.safeParse({ ...valid, fullName: 'J' }).success).toBe(false)); - it('rejects empty name', () => expect(signupSchema.safeParse({ ...valid, fullName: '' }).success).toBe(false)); - it('rejects no uppercase', () => expect(signupSchema.safeParse({ ...valid, password: 'secure1!x', confirmPassword: 'secure1!x' }).success).toBe(false)); - it('rejects no lowercase', () => expect(signupSchema.safeParse({ ...valid, password: 'SECURE1!X', confirmPassword: 'SECURE1!X' }).success).toBe(false)); - it('rejects no number', () => expect(signupSchema.safeParse({ ...valid, password: 'Secure!xx', confirmPassword: 'Secure!xx' }).success).toBe(false)); - it('rejects no special char', () => expect(signupSchema.safeParse({ ...valid, password: 'Secure1xx', confirmPassword: 'Secure1xx' }).success).toBe(false)); - it('rejects password < 8 chars', () => expect(signupSchema.safeParse({ ...valid, password: 'Ab1!', confirmPassword: 'Ab1!' }).success).toBe(false)); - it('rejects mismatched passwords', () => { - const r = signupSchema.safeParse({ ...valid, confirmPassword: 'Other1!' }); - expect(r.success).toBe(false); - if (!r.success) expect(r.error.issues.some(i => i.path.includes('confirmPassword'))).toBe(true); - }); - it('accepts name with accents', () => expect(signupSchema.safeParse({ ...valid, fullName: 'José Álvares' }).success).toBe(true)); - it('accepts complex password', () => expect(signupSchema.safeParse({ ...valid, password: 'C0mpl3x!P@ss', confirmPassword: 'C0mpl3x!P@ss' }).success).toBe(true)); - it('rejects invalid email in signup', () => expect(signupSchema.safeParse({ ...valid, email: 'invalid' }).success).toBe(false)); -}); - -describe('E2E Auth — Protected Route Redirect', () => { - it('unauthenticated users should be redirected', () => { - // Pattern: no user + no canManage → redirect to /login or / - const user = null; - const canManage = false; - expect(!user).toBe(true); - expect(!canManage).toBe(true); - }); -}); - -describe('E2E Auth — Role Mapping', () => { - const roles = ['admin', 'manager', 'vendedor'] as const; - - it('app has exactly 3 roles', () => expect(roles).toHaveLength(3)); - it('admin is a valid role', () => expect(roles).toContain('admin')); - it('manager is a valid role', () => expect(roles).toContain('manager')); - it('vendedor is a valid role', () => expect(roles).toContain('vendedor')); - - it('canManage is true for admin', () => { - const role = 'admin'; - expect(role === 'admin' || role === 'manager').toBe(true); - }); - it('canManage is true for manager', () => { - const role = 'manager'; - expect(role === 'admin' || role === 'manager').toBe(true); - }); - it('canManage is false for vendedor', () => { - const role = 'vendedor'; - expect(role === 'admin' || role === 'manager').toBe(false); - }); -}); - -describe('E2E Auth — IP Validation', () => { - it('valid IPv4', () => expect(/^\d+\.\d+\.\d+\.\d+$/.test('192.168.1.1')).toBe(true)); - it('localhost', () => expect(/^\d+\.\d+\.\d+\.\d+$/.test('127.0.0.1')).toBe(true)); - it('invalid IP', () => expect(/^\d+\.\d+\.\d+\.\d+$/.test('abc')).toBe(false)); -}); diff --git a/tests/e2e/carts-excellence.spec.ts b/tests/e2e/carts-excellence.spec.ts deleted file mode 100644 index c0af519a4..000000000 --- a/tests/e2e/carts-excellence.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Carrinhos E2E Tests - Onda Excelência UX 10/10 - */ - -import { test, expect } from "@playwright/test"; - -test.describe("Módulo de Carrinhos - Fluxos Críticos", () => { - test.beforeEach(async ({ page }) => { - // Login simplificado ou skip auth se estiver em dev - await page.goto("/carrinhos"); - }); - - test("Criação de novo carrinho via Picker", async ({ page }) => { - // Abre modal de novo carrinho - await page.click('[data-testid="cart-tab-new"]'); - await expect(page.locator("text=Vincular a uma empresa")).toBeVisible(); - - // Seleciona uma empresa da busca ou recentes - const firstCompany = page.locator('[data-testid="cart-company-picker-select"]').first(); - await firstCompany.click(); - - // Valida se aba foi criada - await expect(page.locator('[data-testid="cart-tab"][data-active="true"]')).toBeVisible(); - }); - - test("Gestão de itens: Adição, Quantidade e Notas", async ({ page }) => { - // Assume que já existe um carrinho ativo (ou cria um rápido) - // Para teste isolado, vamos garantir um carrinho - await page.goto("/produtos"); - await page.click('[data-testid="product-card-add"]').first(); - await page.goto("/carrinhos"); - - // Valida item no carrinho - const cartItem = page.locator('[data-testid="cart-item"]').first(); - await expect(cartItem).toBeVisible(); - - // Testa Stepper de Quantidade - const initialQty = await page.locator('[data-testid="cart-qty-badge"]').textContent(); - await page.click('[data-testid="cart-qty-increment"]'); - await expect(page.locator('[data-testid="cart-qty-badge"]')).not.toHaveText(initialQty || ""); - - // Testa Notas do Item - await page.click('[data-testid="cart-item-notes-toggle"]'); - await page.fill('[data-testid="cart-item-notes-input"]', "Teste de observação E2E"); - // Aguarda debounce - await page.waitForTimeout(1000); - await page.reload(); - await page.click('[data-testid="cart-item-notes-toggle"]'); - await expect(page.locator('[data-testid="cart-item-notes-input"]')).toHaveValue("Teste de observação E2E"); - }); - - test("Saúde do Carrinho e Conversão para Orçamento", async ({ page }) => { - await page.goto("/carrinhos"); - // Verifica se Checklist de Saúde está visível - await expect(page.locator("text=Saúde do Carrinho")).toBeVisible(); - - // Clica no CTA principal de conversão - await page.click('[data-testid="cart-checkout-cta"]'); - - // Valida Dialog de Confirmação - await expect(page.locator('[data-testid="cart-confirm-dialog"]')).toBeVisible(); - await page.click('button:has-text("Gerar Orçamento")'); - - // Deve redirecionar para o Builder de Orçamento - await expect(page).toHaveURL(/\/orcamentos\/novo/); - }); -}); diff --git a/tests/e2e/catalog-price-freshness-badge.test.tsx b/tests/e2e/catalog-price-freshness-badge.test.tsx deleted file mode 100644 index 683588c22..000000000 --- a/tests/e2e/catalog-price-freshness-badge.test.tsx +++ /dev/null @@ -1,298 +0,0 @@ -/** - * E2E — Visibilidade do PriceFreshnessBadge no fluxo de catálogo. - * - * Percorre os 4 pontos de exibição do catálogo (card, card aprimorado, - * lista, tabela e quick view) usando exatamente as mesmas props que cada - * componente real chama em produção, e valida: - * - * 1. variant="icon-only" (ProductCard, EnhancedProductCard, ProductTableView): - * só renderiza quando aging/stale. - * 2. variant="compact" (ProductListItem): - * só renderiza quando aging/stale. - * 3. variant="inline" + alwaysShow (ProductQuickView): - * SEMPRE renderiza, em qualquer estado (incluindo fresh/unknown). - * - * Os componentes "host" (cards, lista, tabela, quick view) trazem dezenas - * de dependências (gallery, favoritos, comparador, hooks de estoque, - * supplier trust, etc.). Esta suíte E2E preserva o contrato visual do - * catálogo isolando o ponto que importa — o badge — e variando o produto - * pela mesma matriz de freshness percorrida em produção. Se alguém trocar - * a variant em qualquer um dos hosts (ex.: passar inline no card), os - * snapshots dedicados de cada host quebram; se mudarem a regra de - * visibilidade do próprio badge, ESTA suíte quebra primeiro com uma - * mensagem legível ("card icon-only deve ficar invisível em fresh"). - */ -import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from "vitest"; -import { render, screen } from "@testing-library/react"; -import { PriceFreshnessBadge } from "@/components/products/PriceFreshnessBadge"; - -const FIXED_NOW = new Date("2026-04-24T12:00:00.000Z").getTime(); -const daysAgo = (d: number) => - new Date(FIXED_NOW - d * 86400000).toISOString(); - -beforeAll(() => { - vi.useFakeTimers(); - vi.setSystemTime(FIXED_NOW); -}); -afterAll(() => { - vi.useRealTimers(); -}); - -/** - * Conjunto de produtos do catálogo simulado — um por estado de - * freshness. Threshold default 60d (igual ao do banco externo). - */ -const CATALOG = [ - { - id: "fresh", - name: "Caneta Fresh", - price: 5.5, - priceUpdatedAt: daysAgo(5), - priceFreshnessThresholdDays: 60, - }, - { - id: "aging", - name: "Mochila Aging", - price: 89.9, - priceUpdatedAt: daysAgo(45), - priceFreshnessThresholdDays: 60, - }, - { - id: "stale", - name: "Squeeze Stale", - price: 35, - priceUpdatedAt: daysAgo(90), - priceFreshnessThresholdDays: 60, - }, - { - id: "unknown", - name: "Chaveiro Unknown", - price: 8.9, - priceUpdatedAt: null, - priceFreshnessThresholdDays: 60, - }, -] as const; - -/** - * Renderiza a invocação EXATA usada por cada host em produção, - * encapsulando o badge num wrapper data-testid para identificação no - * percurso. Mudança de assinatura em qualquer host quebra também os - * testes específicos daquele host; este E2E foca no contrato de - * visibilidade. - */ -function renderCatalogRow(product: (typeof CATALOG)[number]) { - return render( -
- {/* ProductCard.tsx — variant="icon-only" */} -
- -
- {/* EnhancedProductCard.tsx — também variant="icon-only" */} -
- -
- {/* ProductListItem.tsx — variant="compact" */} -
- -
- {/* ProductTableView.tsx — variant="icon-only" */} -
- -
- {/* ProductQuickView.tsx — variant="inline" + alwaysShow */} -
- -
-
, - ); -} - -/** Conta badges (role=status) dentro de um wrapper testid. */ -function badgeCountIn(testId: string): number { - const wrapper = screen.getByTestId(testId); - return wrapper.querySelectorAll('[role="status"]').length; -} - -describe("E2E Catálogo — visibilidade do PriceFreshnessBadge por host", () => { - describe("Produto FRESH (5d, threshold 60d) — preço dentro do prazo", () => { - beforeEach(() => renderCatalogRow(CATALOG[0])); - - it("Card (icon-only) NÃO renderiza badge", () => { - expect(badgeCountIn("card-fresh")).toBe(0); - }); - it("EnhancedCard (icon-only) NÃO renderiza badge", () => { - expect(badgeCountIn("enhanced-card-fresh")).toBe(0); - }); - it("Lista (compact) NÃO renderiza badge", () => { - expect(badgeCountIn("list-fresh")).toBe(0); - }); - it("Tabela (icon-only) NÃO renderiza badge", () => { - expect(badgeCountIn("table-fresh")).toBe(0); - }); - it("Quick View (inline + alwaysShow) RENDERIZA badge mesmo em fresh", () => { - expect(badgeCountIn("quickview-fresh")).toBe(1); - const badge = screen.getByTestId("quickview-fresh").querySelector('[role="status"]')!; - expect(badge.textContent).toMatch(/atualizado/i); - expect(badge.className).toMatch(/emerald-(200|400|500|700)/); - }); - }); - - describe("Produto AGING (45d, threshold 60d) — próximo do limite", () => { - beforeEach(() => renderCatalogRow(CATALOG[1])); - - it("Card (icon-only) RENDERIZA badge amber", () => { - expect(badgeCountIn("card-aging")).toBe(1); - const badge = screen.getByTestId("card-aging").querySelector('[role="status"]')!; - expect(badge.className).toMatch(/text-amber-700/); - }); - it("EnhancedCard (icon-only) RENDERIZA badge amber", () => { - expect(badgeCountIn("enhanced-card-aging")).toBe(1); - }); - it("Lista (compact) RENDERIZA badge com 'há Nd'", () => { - expect(badgeCountIn("list-aging")).toBe(1); - const badge = screen.getByTestId("list-aging").querySelector('[role="status"]')!; - expect(badge.textContent).toMatch(/há \d+[dma]/); - }); - it("Tabela (icon-only) RENDERIZA badge amber", () => { - expect(badgeCountIn("table-aging")).toBe(1); - }); - it("Quick View (inline + alwaysShow) RENDERIZA badge", () => { - expect(badgeCountIn("quickview-aging")).toBe(1); - }); - }); - - describe("Produto STALE (90d, threshold 60d) — preço defasado", () => { - beforeEach(() => renderCatalogRow(CATALOG[2])); - - it("Card (icon-only) RENDERIZA badge de alerta", () => { - expect(badgeCountIn("card-stale")).toBe(1); - const badge = screen.getByTestId("card-stale").querySelector('[role="status"]')!; - expect(badge.getAttribute("aria-label")).toMatch(/possivelmente defasado/i); - }); - it("EnhancedCard (icon-only) RENDERIZA badge de alerta", () => { - expect(badgeCountIn("enhanced-card-stale")).toBe(1); - }); - it("Lista (compact) RENDERIZA badge de alerta", () => { - expect(badgeCountIn("list-stale")).toBe(1); - }); - it("Tabela (icon-only) RENDERIZA badge de alerta", () => { - expect(badgeCountIn("table-stale")).toBe(1); - }); - it("Quick View (inline + alwaysShow) RENDERIZA com cópia 'defasado'", () => { - expect(badgeCountIn("quickview-stale")).toBe(1); - const badge = screen.getByTestId("quickview-stale").querySelector('[role="status"]')!; - expect(badge.textContent).toMatch(/preço pode estar defasado/i); - }); - }); - - describe("Produto UNKNOWN (sem priceUpdatedAt) — data não informada", () => { - beforeEach(() => renderCatalogRow(CATALOG[3])); - - it("Card (icon-only) NÃO renderiza badge", () => { - expect(badgeCountIn("card-unknown")).toBe(0); - }); - it("EnhancedCard (icon-only) NÃO renderiza badge", () => { - expect(badgeCountIn("enhanced-card-unknown")).toBe(0); - }); - it("Lista (compact) NÃO renderiza badge", () => { - expect(badgeCountIn("list-unknown")).toBe(0); - }); - it("Tabela (icon-only) NÃO renderiza badge", () => { - expect(badgeCountIn("table-unknown")).toBe(0); - }); - it("Quick View (inline + alwaysShow) RENDERIZA badge mesmo sem data", () => { - // alwaysShow tem que vencer o status unknown — vendedor precisa - // saber que a data não foi informada quando abre o quick view. - expect(badgeCountIn("quickview-unknown")).toBe(1); - const badge = screen.getByTestId("quickview-unknown").querySelector('[role="status"]')!; - expect(badge.textContent).toMatch(/data de atualização não informada/i); - }); - }); - - describe("Invariante de catálogo (todos os produtos numa só passada)", () => { - it("hosts compactos (card/lista/tabela) só somam badges para aging+stale", () => { - // Renderiza catálogo inteiro (4 produtos × 5 hosts = 20 slots). - render( -
- {CATALOG.map((p) => ( -
-
- -
-
- -
-
- -
-
- -
-
- ))} -
, - ); - - const catalog = screen.getByTestId("full-catalog"); - // Compactos: 2 produtos visíveis (aging + stale) × 3 hosts = 6 badges. - const compactBadges = ["card", "list", "table"].flatMap((host) => - CATALOG.map((p) => - catalog - .querySelector(`[data-testid="row-${host}-${p.id}"]`)! - .querySelectorAll('[role="status"]').length, - ), - ); - const compactTotal = compactBadges.reduce((a, b) => a + b, 0); - expect(compactTotal).toBe(6); - - // Quick View com alwaysShow: 4 produtos × 1 = 4 badges. - const quickviewTotal = CATALOG.reduce( - (acc, p) => - acc + - catalog - .querySelector(`[data-testid="row-quickview-${p.id}"]`)! - .querySelectorAll('[role="status"]').length, - 0, - ); - expect(quickviewTotal).toBe(4); - }); - }); -}); diff --git a/tests/e2e/compare-exhaustive.test.ts b/tests/e2e/compare-exhaustive.test.ts deleted file mode 100644 index 80c13b07f..000000000 --- a/tests/e2e/compare-exhaustive.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from 'fs'; -import path from 'path'; - -/** - * Módulo: Comparar (E2E Exaustivo) - * Objetivo: Validar todas as funcionalidades, botões e camadas do sistema de comparação. - */ - -test.describe("Módulo de Comparação - Testes Exaustivos e Minuciosos", () => { - - const evidenceDir = 'tests/e2e/evidence/exhaustive'; - - test.beforeAll(async () => { - if (!fs.existsSync(evidenceDir)) { - fs.mkdirSync(evidenceDir, { recursive: true }); - } - }); - - test.beforeEach(async ({ page }) => { - // Configura captura de logs para auditoria - page.on('console', msg => { - const logLine = `[${msg.type()}] ${msg.text()}\n`; - fs.appendFileSync(path.join(evidenceDir, 'exhaustive-audit.log'), logLine); - }); - - // Passo 1: Adicionar produtos à comparação a partir do catálogo - await page.goto("/produtos"); - const compareButtons = page.locator('button[aria-label*="comparar"], button:has-text("Comparar")'); - - // Adicionamos 4 produtos (limite máximo para testar todos os estados) - for (let i = 0; i < 4; i++) { - await compareButtons.nth(i).click(); - await page.waitForTimeout(100); // Pequena pausa para garantir o registro no store - } - - await page.goto("/comparar"); - }); - - test("Fluxo 1: Gestão de Itens e Limites", async ({ page }) => { - // Valida que 4 produtos estão sendo comparados - await expect(page.locator("text=Comparando 4 produtos")).toBeVisible(); - await page.screenshot({ path: path.join(evidenceDir, '01-initial-4-products.png') }); - - // Testa remoção individual - const removeButtons = page.locator('button[aria-label="Remover"]'); - await removeButtons.first().click(); - await expect(page.locator("text=Comparando 3 produtos")).toBeVisible(); - - // Testa botão Limpar Tudo - await page.click("text=Limpar"); - await expect(page.locator("text=Selecione pelo menos 2 produtos")).toBeVisible(); - await page.screenshot({ path: path.join(evidenceDir, '02-after-clear.png') }); - }); - - test("Fluxo 2: Visualização de Dados e Filtros Avançados", async ({ page }) => { - // Alterna para Tabela Detalhada - await page.click("text=Tabela Detalhada"); - - // Valida presença de Atributos Críticos - const attributes = ["Preço unitário", "Quantidade mínima", "Estoque", "Lead time", "SKU"]; - for (const attr of attributes) { - await expect(page.locator(`text=${attr}`)).toBeVisible(); - } - - // Testa Filtro 'Só diferenças' - await page.click("text=Só diferenças"); - await expect(page.locator("text=Mostrando diferenças")).toBeVisible(); - - // Verifica se as linhas que eram iguais sumiram (SKU geralmente é diferente, Categoria pode ser igual) - // Se todos forem da mesma categoria, a linha 'Categoria' deve sumir no modo 'Só diferenças' - await page.screenshot({ path: path.join(evidenceDir, '03-table-differences-only.png') }); - }); - - test("Fluxo 3: Inteligência e Gráficos (Radar + Advisor)", async ({ page }) => { - // Valida Radar Chart (Recharts) - const radar = page.locator(".recharts-responsive-container"); - await expect(radar).toBeVisible(); - - // Valida Score Cards - await expect(page.locator("text=Pontuação de Comparação")).toBeVisible(); - - // Valida Advisor AI (seção de recomendações inteligentes) - await expect(page.locator("text=Advisor AI").or(page.locator("text=Recomendação"))).toBeVisible(); - - // Testa toggle do Radar via atalho 'R' - await page.keyboard.press("r"); - await expect(radar).not.toBeVisible(); - await page.keyboard.press("r"); - await expect(radar).toBeVisible(); - }); - - test("Fluxo 4: Exportação e Compartilhamento", async ({ page }) => { - // Testa abertura do Modal de Compartilhamento - await page.click("text=Compartilhar"); - await expect(page.locator("text=Link de Comparação")).toBeVisible(); - await page.click('button[aria-label="Close"]'); // Fecha modal - - // Testa Exportação (validação de botão presente) - const exportBtn = page.locator("text=Exportar").or(page.locator('button[aria-label*="Exportar"]')); - await expect(exportBtn).toBeVisible(); - }); - - test("Fluxo 5: Responsividade e Sincronização de Variantes", async ({ page }) => { - // Simula Mobile Viewport - await page.setViewportSize({ width: 375, height: 812 }); - // No mobile, a visualização muda para o Carrossel (ComparisonMobileView) - await expect(page.locator(".md\\:hidden")).toBeVisible(); - await page.screenshot({ path: path.join(evidenceDir, '04-mobile-carousel.png') }); - - // Volta para Desktop - await page.setViewportSize({ width: 1280, height: 720 }); - }); - - test("Fluxo 6: Integração CRM e Cliente", async ({ page }) => { - // Testa seleção de cliente CRM - await page.click("text=Cliente CRM"); - // Seleciona o primeiro cliente da lista (se houver) - const clientOption = page.locator("role=menuitem").or(page.locator("button")).filter({ hasText: /Cliente/i }).first(); - if (await clientOption.isVisible()) { - await clientOption.click(); - await expect(page.locator("text=Comparando")).toContainText(/Cliente/i); - } - }); - -}); diff --git a/tests/e2e/compare-exhaustive.test.tsx b/tests/e2e/compare-exhaustive.test.tsx deleted file mode 100644 index 2aadd4cc0..000000000 --- a/tests/e2e/compare-exhaustive.test.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import ComparePage from '../../src/pages/products/ComparePage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -// Mock window.scrollTo -window.scrollTo = vi.fn(); - -// Mock de produtos base -const mockProducts = [ - { - id: 'p1', name: 'Produto 1', price: 100, images: ['img1.jpg'], - minQuantity: 10, stock: 500, stockStatus: 'in-stock', - colors: [{ name: 'Azul', hex: '#0000ff' }], sku: 'SKU1', - category: { name: 'Escritório', icon: '📎' }, - supplier: { name: 'Sup1', verified: true } - }, - { - id: 'p2', name: 'Produto 2', price: 150, images: ['img2.jpg'], - minQuantity: 5, stock: 20, stockStatus: 'low-stock', - colors: [{ name: 'Vermelho', hex: '#ff0000' }], sku: 'SKU2', - category: { name: 'Escritório', icon: '📎' }, - supplier: { name: 'Sup2', verified: false } - } -]; - -// Mocks consolidados -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false, startTour: vi.fn(), completeTour: vi.fn() }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [], addItem: vi.fn(), removeItem: vi.fn() }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), - order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), - maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((onFulfilled) => Promise.resolve({ data: [] }).then(onFulfilled)), - }; - return { - supabase: { - auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }) }, - from: vi.fn().mockReturnValue(chain), - rpc: vi.fn().mockResolvedValue({ data: [] }), - }, - }; -}); - -vi.mock('../../src/contexts/ProductsContext', () => ({ - useProductsContext: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - useProductsContextSafe: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - ProductsContext: { Provider: ({ children }: any) =>
{children}
}, - ProductsProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('recharts', () => ({ - ResponsiveContainer: ({ children }: any) =>
{children}
, - Radar: () =>
, - RadarChart: ({ children }: any) =>
{children}
, - PolarGrid: () =>
, PolarAngleAxis: () =>
, PolarRadiusAxis: () =>
, - Legend: () =>
, Tooltip: () =>
, - LineChart: ({ children }: any) =>
{children}
, - Line: () =>
, -})); - -vi.mock('../../src/hooks/comparison/useComparisonScore', () => ({ - useComparisonScore: (products: any[]) => (products || []).map(p => ({ - productId: String(p.id), total: 80, score: 80, isWinner: true, rank: 1, - breakdown: { price: 35, stock: 20, minQuantity: 15, colorVariety: 10, verifiedSupplier: 10, leadTime: 10 } - })), - DEFAULT_SCORE_WEIGHTS: { price: 35, stock: 20, minQuantity: 15, colorVariety: 10, verifiedSupplier: 10, leadTime: 10 } -})); - -describe('Módulo Comparar - Bateria Exaustiva', () => { - beforeEach(() => { - vi.clearAllMocks(); - useComparisonStore.setState({ - compareItems: [{ productId: 'p1' }, { productId: 'p2' }], - compareCount: 2, compareIds: ['p1', 'p2'], isLoaded: true, - }); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Geral: Carregamento e Interface Base', async () => { - await renderPage(); - expect(await screen.findByText(/Comparador de Produtos/i)).toBeInTheDocument(); - }); - - it('Modos: Duelo', async () => { - await renderPage(); - const duelBtn = await screen.findByText(/Ativar Modo Duelo/i); - fireEvent.click(duelBtn); - expect(await screen.findByText(/Modo Duelo ativo/i)).toBeInTheDocument(); - }); -}); - - - - diff --git a/tests/e2e/compare-module.test.ts b/tests/e2e/compare-module.test.ts deleted file mode 100644 index c04d25997..000000000 --- a/tests/e2e/compare-module.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from 'fs'; -import path from 'path'; - -/** - * Módulo: Comparar (E2E Avançado) - * Cenários: Transições visuais, Erros de Radar, Performance e Logs de Warning. - */ - -test.describe("Módulo de Comparação - Testes de Regressão Visual e Robustez", () => { - - const evidenceDir = 'tests/e2e/evidence'; - - test.beforeAll(async () => { - if (!fs.existsSync(evidenceDir)) { - fs.mkdirSync(evidenceDir, { recursive: true }); - } - }); - - test.beforeEach(async ({ page }) => { - // Captura logs do console para detectar warnings do React - page.on('console', msg => { - if (msg.type() === 'warning' || msg.type() === 'error') { - const text = msg.text(); - if (text.includes('act') || text.includes('update during render') || text.includes('React Router')) { - console.warn(`[REACT WARNING] ${text}`); - fs.appendFileSync(path.join(evidenceDir, 'console-warnings.log'), `${new Date().toISOString()} - ${text}\n`); - } - } - }); - - await page.goto("/produtos"); - const compareButtons = page.locator('button[aria-label*="comparar"], button:has-text("Comparar")'); - await compareButtons.nth(0).click(); - await compareButtons.nth(1).click(); - await page.goto("/comparar"); - }); - - test("Cenário: Regressão Visual - Transição Duelo para Galeria (3º produto)", async ({ page }) => { - // Reduz animações para screenshots consistentes - await page.addStyleTag({ content: '*, *::before, *::after { transition-duration: 0.001s !important; animation-duration: 0.001s !important; transition-delay: 0s !important; }' }); - - // Screenshot do estado inicial (Duelo com 2 produtos) - await expect(page.locator("text=⚔️ Modo Duelo").or(page.locator("text=Ativar Modo Duelo"))).toBeVisible(); - await page.screenshot({ path: path.join(evidenceDir, 'compare-duel-initial.png') }); - - // Adiciona o 3º produto via rail ou navegação - await page.goto("/produtos"); - const compareButtons = page.locator('button[aria-label*="comparar"], button:has-text("Comparar")'); - await compareButtons.nth(2).click(); - await page.goto("/comparar"); - - // Valida transição visual automática - await expect(page.locator("text=Galeria Visual")).toBeVisible(); - await page.screenshot({ path: path.join(evidenceDir, 'compare-gallery-3-products.png'), fullPage: true }); - - // Snapshot do DOM para análise de integridade - const dom = await page.content(); - fs.writeFileSync(path.join(evidenceDir, 'compare-3-products-snapshot.html'), dom); - - // Verifica se o Modo Duelo não está mais visível - await expect(page.locator("text=⚔️ Modo Duelo")).not.toBeVisible(); - }); - - test("Cenário: Erro e Timeout no Radar Chart", async ({ page }) => { - // Mock de erro na RPC de top produtos para testar resiliência - await page.route('**/rest/v1/rpc/get_top_compared_products*', route => { - route.abort('timedout'); - }); - - await page.reload(); - - // Verifica se a UI exibe o fallback graciosamente em vez de quebrar - // Nota: O fallback exibe "Sugestões para começar" se a RPC falhar - const emptyState = page.locator("text=Sugestões para começar").or(page.locator("text=Os mais comparados")); - await expect(emptyState).toBeVisible(); - - await page.screenshot({ path: path.join(evidenceDir, 'radar-error-fallback.png') }); - }); - - test("Cenário: Performance e Persistência (Galeria vs Tabela)", async ({ page }) => { - // Alterna para Tabela - await page.click("text=Tabela Detalhada"); - await expect(page.locator("table")).toBeVisible(); - - // Inicia medição de tempo de resposta para filtros - const start = Date.now(); - await page.click("text=Só diferenças"); - await expect(page.locator("text=Mostrando diferenças")).toBeVisible(); - const duration = Date.now() - start; - - console.log(`[PERF] Filtro 'Só diferenças' aplicado em ${duration}ms`); - expect(duration).toBeLessThan(1000); - - // Valida se o cabeçalho sticky funciona ao rolar - await page.evaluate(() => window.scrollTo(0, 800)); - // O sticky mini-header aparece quando o Sentinel sai de cena - const stickyHeader = page.locator('div[class*="sticky top-0"]'); - await expect(stickyHeader).toBeVisible(); - await page.screenshot({ path: path.join(evidenceDir, 'sticky-header-active.png') }); - }); - - test("Cenário: Cleanup e Acessibilidade", async ({ page }) => { - const ariaLive = page.locator('[aria-live="polite"]'); - const clearBtn = page.locator("text=Limpar"); - await clearBtn.click(); - - // Após limpar, deve voltar para o catálogo ou estado vazio - await expect(page.locator("text=Comparador de Produtos")).toBeVisible(); - await expect(page.locator("text=Selecione pelo menos 2 produtos")).toBeVisible(); - - await page.screenshot({ path: path.join(evidenceDir, 'cleanup-state.png') }); - }); -}); - - diff --git a/tests/e2e/compare-module.test.tsx b/tests/e2e/compare-module.test.tsx deleted file mode 100644 index b244a2b8b..000000000 --- a/tests/e2e/compare-module.test.tsx +++ /dev/null @@ -1,330 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import ComparePage from '../../src/pages/products/ComparePage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, -}); - -// Mock window.scrollTo -window.scrollTo = vi.fn(); - -// Mock do contexto de Auth -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ - user: { id: 'user-123' }, - session: { access_token: 'fake-token' }, - profile: { id: 'prof-123', full_name: 'Test User' }, - isLoading: false, - isAdmin: false, - role: 'agente', - isAuthenticated: true, - }), - AuthProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, -})); - -// Mock do contexto de AriaLive -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ - announce: vi.fn(), - announceStatus: vi.fn(), - announceAlert: vi.fn(), - announceProgress: vi.fn(), - }), - AriaLiveProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, -})); - -// Mock do contexto de Onboarding -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ - isTourOpen: false, - startTour: vi.fn(), - completeTour: vi.fn(), - }), - useOnboardingContext: () => ({ - isTourOpen: false, - startTour: vi.fn(), - useOptionalOnboardingContext: () => null, - completeTour: vi.fn(), - }), - useOnboardingHook: () => ({ - isTourOpen: false, - startTour: vi.fn(), - completeTour: vi.fn(), - }), - OnboardingProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, -})); - -// Mock do contexto de Carrinho do Vendedor -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ - items: [], - addItem: vi.fn(), - removeItem: vi.fn(), - }), - SellerCartProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, -})); - -// Mock do Supabase -vi.mock('../../src/integrations/supabase/client', () => ({ - supabase: { - auth: { - getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'user-123' } } }), - }, - from: vi.fn().mockReturnThis(), - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), - order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), - maybeSingle: vi.fn().mockResolvedValue({ data: null }), - rpc: vi.fn().mockResolvedValue({ data: [] }), - }, -})); - -// Mock do Store -const mockProducts = [ - { - id: 'prod-1', - name: 'Produto A', - price: 100, - images: ['img1.jpg'], - minQuantity: 10, - stock: 500, - stockStatus: 'in-stock', - colors: [{ name: 'Azul', hex: '#0000ff' }], - sku: 'SKU-A', - category: { name: 'Escritório', icon: '📎' }, - supplier: { name: 'Fornecedor X', verified: true }, - }, - { - id: 'prod-2', - name: 'Produto B', - price: 150, - images: ['img2.jpg'], - minQuantity: 5, - stock: 20, - stockStatus: 'low-stock', - colors: [{ name: 'Vermelho', hex: '#ff0000' }], - sku: 'SKU-B', - category: { name: 'Escritório', icon: '📎' }, - supplier: { name: 'Fornecedor Y', verified: false }, - }, - { - id: 'prod-3', - name: 'Produto C', - price: 120, - images: ['img3.jpg'], - minQuantity: 15, - stock: 100, - stockStatus: 'in-stock', - colors: [{ name: 'Verde', hex: '#00ff00' }], - sku: 'SKU-C', - category: { name: 'Escritório', icon: '📎' }, - supplier: { name: 'Fornecedor Z', verified: true }, - } -]; - -// Mock do ProductsContext -vi.mock('../../src/contexts/ProductsContext', () => ({ - useProductsContext: () => ({ - products: mockProducts, - getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)), - isLoading: false, - getProductById: (id: string) => mockProducts.find(p => p.id === id), - }), - useProductsContextSafe: () => ({ - products: mockProducts, - getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)), - isLoading: false, - getProductById: (id: string) => mockProducts.find(p => p.id === id), - }), - ProductsContext: { - Provider: ({ children }: { children: React.ReactNode }) =>
{children}
, - }, - ProductsProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, -})); - -// Mock do hook useComparisonScore -vi.mock('../../src/hooks/comparison/useComparisonScore', () => ({ - useComparisonScore: (products: any[]) => { - if (!products || products.length === 0) return []; - return products.map(p => ({ - productId: String(p.id), - total: 80, - score: 80, - isWinner: p.id === 'prod-1', - rank: 1, - breakdown: { price: 35, stock: 20, minQuantity: 15, colorVariety: 10, verifiedSupplier: 10, leadTime: 10 } - })); - }, - DEFAULT_SCORE_WEIGHTS: { price: 35, stock: 20, minQuantity: 15, colorVariety: 10, verifiedSupplier: 10, leadTime: 10 } -})); - -// Mock da biblioteca de Recharts -vi.mock('recharts', () => ({ - ResponsiveContainer: ({ children }: any) =>
{children}
, - Radar: () =>
, - RadarChart: () =>
, - PolarGrid: () =>
, - PolarAngleAxis: () =>
, - PolarRadiusAxis: () =>
, - Legend: () =>
, - Tooltip: () =>
, -})); - -describe('E2E Comparar — Módulo de Comparação', () => { - beforeEach(() => { - vi.clearAllMocks(); - useComparisonStore.setState({ - compareItems: [], - compareIds: [], - compareCount: 0, - isLoaded: true, - }); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - // Aguarda microtasks para evitar warnings de act() em hooks assíncronos - await waitFor(() => {}); - return res; - }; - - - it('exibe estado vazio inteligente quando menos de 2 produtos estão na comparação', async () => { - await renderPage(); - expect(screen.getByText(/Selecione pelo menos 2 produtos para comparar/i)).toBeInTheDocument(); - expect(screen.getByText(/Explorar catálogo/i)).toBeInTheDocument(); - }); - - - it('exibe a tabela de comparação quando 2 ou mais produtos são adicionados', async () => { - useComparisonStore.setState({ - compareItems: [ - { productId: 'prod-1' }, - { productId: 'prod-2' } - ], - compareCount: 2, - compareIds: ['prod-1', 'prod-2'], - }); - - await renderPage(); - - await waitFor(() => { - expect(screen.getByText(/Comparador de Produtos/i)).toBeInTheDocument(); - expect(screen.getByText(/Comparando 2 produtos/i)).toBeInTheDocument(); - }); - }); - - it('valida o Modo Duelo quando exatamente 2 produtos estão presentes', async () => { - useComparisonStore.setState({ - compareItems: [{ productId: 'prod-1' }, { productId: 'prod-2' }], - compareCount: 2, - compareIds: ['prod-1', 'prod-2'], - }); - - await renderPage(); - - await waitFor(() => { - const duelBtns = screen.getAllByText(/Modo Duelo/i); - expect(duelBtns.length).toBeGreaterThan(0); - }); - }); - - it('permite alternar entre Galeria Visual e Tabela Detalhada', async () => { - useComparisonStore.setState({ - compareItems: [{ productId: 'prod-1' }, { productId: 'prod-2' }, { productId: 'prod-3' }], - compareCount: 3, - compareIds: ['prod-1', 'prod-2', 'prod-3'], - }); - - await renderPage(); - - const tableTab = await screen.findByText(/Tabela Detalhada/i); - fireEvent.click(tableTab); - - // Na tabela detalhada, o modo de exibição muda para a tabela detalhada que contém o texto "Atributo" - await waitFor(() => { - // Usamos queryAllByText para evitar erro de múltiplos elementos se existirem - const attributes = screen.queryAllByText(/Atributo/i); - // Se não achar por texto, tentamos por testid ou outro seletor - if (attributes.length === 0) { - // Fallback se o componente usar tradução ou algo similar - expect(screen.getByText(/Comparador de Produtos/i)).toBeInTheDocument(); - } else { - expect(attributes.length).toBeGreaterThan(0); - } - }); - }); - - it('valida o filtro "Só diferenças"', async () => { - useComparisonStore.setState({ - compareItems: [{ productId: 'prod-1' }, { productId: 'prod-2' }, { productId: 'prod-3' }], - compareCount: 3, - compareIds: ['prod-1', 'prod-2', 'prod-3'], - }); - - await renderPage(); - - const diffBtn = await screen.findByText(/Só diferenças/i); - fireEvent.click(diffBtn); - - await waitFor(() => { - expect(diffBtn).toHaveTextContent(/Mostrando diferenças/i); - }); - }); - - it('permite limpar a comparação', async () => { - const clearSpy = vi.spyOn(useComparisonStore.getState(), 'clearCompare'); - - useComparisonStore.setState({ - compareItems: [{ productId: 'prod-1' }, { productId: 'prod-2' }], - compareCount: 2, - compareIds: ['prod-1', 'prod-2'], - }); - - await renderPage(); - - const clearBtn = await screen.findByText(/Limpar/i); - fireEvent.click(clearBtn); - - expect(clearSpy).toHaveBeenCalled(); - }); - - it('valida acessibilidade: rótulos e navegação por teclado', async () => { - useComparisonStore.setState({ - compareItems: [{ productId: 'prod-1' }, { productId: 'prod-2' }], - compareCount: 2, - compareIds: ['prod-1', 'prod-2'], - }); - - await renderPage(); - - const backBtn = await screen.findByLabelText(/Voltar/i); - expect(backBtn).toBeInTheDocument(); - - backBtn.focus(); - expect(document.activeElement).toBe(backBtn); - }); -}); diff --git a/tests/e2e/compare-ultra.test.ts b/tests/e2e/compare-ultra.test.ts deleted file mode 100644 index bcffbdc2b..000000000 --- a/tests/e2e/compare-ultra.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from 'fs'; -import path from 'path'; - -/** - * Módulo: Comparar (E2E Ultra Avançado) - * Foco: Performance, Acessibilidade, Advisor AI Robustez e Artefatos de Debug. - */ - -test.describe("Módulo de Comparação - Performance & A11y & Robustez", () => { - const artifactDir = 'tests/e2e/artifacts/compare'; - - test.beforeAll(async () => { - if (!fs.existsSync(artifactDir)) fs.mkdirSync(artifactDir, { recursive: true }); - }); - - const saveArtifacts = async (page, name) => { - const html = await page.content(); - const computedStyles = await page.evaluate(() => { - const styles = {}; - document.querySelectorAll('*').forEach((el, i) => { - if (i < 100) { // Limita para evitar payload gigante, foca no topo - const selector = el.id ? `#${el.id}` : `${el.tagName.toLowerCase()}.${Array.from(el.classList).join('.')}`; - styles[selector] = window.getComputedStyle(el).cssText; - } - }); - return styles; - }); - fs.writeFileSync(path.join(artifactDir, `${name}.html`), html); - fs.writeFileSync(path.join(artifactDir, `${name}-styles.json`), JSON.stringify(computedStyles, null, 2)); - await page.screenshot({ path: path.join(artifactDir, `${name}.png`), fullPage: true }); - }; - - test.beforeEach(async ({ page }) => { - // Estado base: 2 produtos - await page.goto("/produtos"); - const btns = page.locator('button[aria-label*="comparar"], button:has-text("Comparar")'); - await btns.nth(0).click(); - await btns.nth(1).click(); - await page.goto("/comparar"); - await page.addStyleTag({ content: '*, *::before, *::after { transition: none !important; animation: none !important; }' }); - }); - - test("Acessibilidade & Foco: Transição Duelo -> Galeria", async ({ page }) => { - // Valida foco inicial - await expect(page.locator("text=Modo Duelo")).toBeVisible(); - - // Adiciona 3º produto e volta - await page.goto("/produtos"); - await page.locator('button[aria-label*="comparar"], button:has-text("Comparar")').nth(2).click(); - await page.goto("/comparar"); - - // Verifica se o título da nova view é anunciado ou recebe foco - const title = page.locator("h1"); - await expect(title).toBeVisible(); - - // Teste de navegação por teclado na nova view - await page.keyboard.press("Tab"); - const focused = await page.evaluate(() => document.activeElement?.tagName); - expect(focused).toBeDefined(); - - // Aria-live check - const ariaLive = page.locator('[aria-live="polite"]'); - await expect(ariaLive).toBeVisible(); - - await saveArtifacts(page, 'a11y-transition-gallery'); - }); - - test("Performance: Renderização Tabela Detalhada com Dados no Limite", async ({ page }) => { - // Adiciona o máximo (4 produtos) - await page.goto("/produtos"); - await page.locator('button[aria-label*="comparar"], button:has-text("Comparar")').nth(2).click(); - await page.locator('button[aria-label*="comparar"], button:has-text("Comparar")').nth(3).click(); - await page.goto("/comparar"); - - const startTime = Date.now(); - await page.click("text=Tabela Detalhada"); - await expect(page.locator("table")).toBeVisible(); - const duration = Date.now() - startTime; - - console.log(`[PERF] Tabela Detalhada renderizada em ${duration}ms`); - // Assert de performance: deve carregar em menos de 1s após o clique (já com dados no store) - expect(duration).toBeLessThan(1000); - - await saveArtifacts(page, 'perf-table-limit'); - }); - - test("Advisor AI: Retry & Recuperação após Timeout", async ({ page }) => { - let callCount = 0; - // Simula falha inicial e depois sucesso para testar retry/recuperação - await page.route('**/functions/v1/ai-advisor*', async route => { - callCount++; - if (callCount === 1) { - await route.abort('timedout'); - } else { - await route.fulfill({ status: 200, body: JSON.stringify({ advice: "Recomendação recuperada com sucesso." }) }); - } - }); - - await page.reload(); - - // Valida que a UI mostra estado de tentativa ou erro amigável - await expect(page.locator("text=Advisor AI").or(page.locator("text=IA"))).toBeVisible(); - - // Se houver um botão de "Tentar novamente", clica. Se for auto-retry, apenas aguarda. - const retryBtn = page.locator("button:has-text('Tentar')"); - if (await retryBtn.isVisible()) { - await retryBtn.click(); - } - - await expect(page.locator("text=Recomendação recuperada")).toBeVisible(); - await saveArtifacts(page, 'ai-recovery-flow'); - }); - - test.afterEach(async ({ page }, testInfo) => { - if (testInfo.status !== testInfo.expectedStatus) { - await saveArtifacts(page, `FAIL-${testInfo.title.replace(/\s+/g, '-')}`); - } - }); -}); diff --git a/tests/e2e/compare-ultra.test.tsx b/tests/e2e/compare-ultra.test.tsx deleted file mode 100644 index b4de80946..000000000 --- a/tests/e2e/compare-ultra.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import ComparePage from '../../src/pages/products/ComparePage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -// Mock window.scrollTo -window.scrollTo = vi.fn(); - -// Mocks consolidados -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false, startTour: vi.fn(), completeTour: vi.fn() }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }) }, - from: vi.fn().mockReturnValue(chain), rpc: vi.fn().mockResolvedValue({ data: [] }), - }, - }; -}); - -const mockProducts = [ - { id: 'p1', name: 'Prod 1', price: 10, images: ['i1.jpg'], minQuantity: 1, stock: 10, stockStatus: 'in-stock', colors: [], sku: 'S1', category: { name: 'C1' }, supplier: { name: 'S1' } }, - { id: 'p2', name: 'Prod 2', price: 20, images: ['i2.jpg'], minQuantity: 2, stock: 20, stockStatus: 'in-stock', colors: [], sku: 'S2', category: { name: 'C1' }, supplier: { name: 'S1' } } -]; - -vi.mock('../../src/contexts/ProductsContext', () => ({ - useProductsContext: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - useProductsContextSafe: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - ProductsContext: { Provider: ({ children }: any) =>
{children}
}, - ProductsProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('recharts', () => ({ - ResponsiveContainer: ({ children }: any) =>
{children}
, - Radar: () =>
, RadarChart: ({ children }: any) =>
{children}
, - PolarGrid: () =>
, PolarAngleAxis: () =>
, PolarRadiusAxis: () =>
, - Legend: () =>
, Tooltip: () =>
, - LineChart: ({ children }: any) =>
{children}
, Line: () =>
, -})); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -describe('Módulo Comparar - Ultra Avançado', () => { - beforeEach(() => { - vi.clearAllMocks(); - useComparisonStore.setState({ - compareItems: [{ productId: 'p1' }, { productId: 'p2' }], - compareCount: 2, compareIds: ['p1', 'p2'], isLoaded: true, - }); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Performance: Renderiza interface principal sob carga simulada', async () => { - const t0 = performance.now(); - await renderPage(); - const t1 = performance.now(); - console.log(`Render time: ${t1 - t0}ms`); - expect(await screen.findByText(/Comparador de Produtos/i)).toBeInTheDocument(); - expect(t1 - t0).toBeLessThan(1000); // Assert de performance - }); - - it('Acessibilidade: Elementos críticos possuem rótulos adequados', async () => { - await renderPage(); - expect(screen.getByLabelText(/Voltar/i)).toBeInTheDocument(); - const duelBtns = await screen.findAllByText(/Modo Duelo/i); - expect(duelBtns.length).toBeGreaterThan(0); - }); - - it('Resiliência AI: Carrega Advisor AI graciosamente', async () => { - await renderPage(); - // O componente usa o termo "Conselheiro IA" - expect(await screen.findByText(/Conselheiro IA/i)).toBeInTheDocument(); - expect(screen.getByText(/Analisar com IA/i)).toBeInTheDocument(); - }); -}); - - - diff --git a/tests/e2e/compare-viewer-a11y.test.tsx b/tests/e2e/compare-viewer-a11y.test.tsx deleted file mode 100644 index 9a7cdaf9e..000000000 --- a/tests/e2e/compare-viewer-a11y.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import ComparePage from '../../src/pages/products/ComparePage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import fs from 'fs'; -import path from 'path'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -window.scrollTo = vi.fn(); - -// Mocks consolidados para estabilidade -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false, startTour: vi.fn(), completeTour: vi.fn() }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }) }, - from: vi.fn().mockReturnValue(chain), rpc: vi.fn().mockResolvedValue({ data: [] }), - }, - }; -}); - -const mockProducts = [ - { id: 'p1', name: 'Prod 1', price: 10, images: ['i1.jpg'], minQuantity: 1, stock: 10, stockStatus: 'in-stock', colors: [], sku: 'S1', category: { name: 'C1' }, supplier: { name: 'S1' } }, - { id: 'p2', name: 'Prod 2', price: 20, images: ['i2.jpg'], minQuantity: 2, stock: 20, stockStatus: 'in-stock', colors: [], sku: 'S2', category: { name: 'C1' }, supplier: { name: 'S1' } } -]; - -vi.mock('../../src/contexts/ProductsContext', () => ({ - useProductsContext: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - useProductsContextSafe: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - ProductsContext: { Provider: ({ children }: any) =>
{children}
}, - ProductsProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('recharts', () => ({ - ResponsiveContainer: ({ children }: any) =>
{children}
, - Radar: () =>
, RadarChart: ({ children }: any) =>
{children}
, - PolarGrid: () =>
, PolarAngleAxis: () =>
, PolarRadiusAxis: () =>
, - Legend: () =>
, Tooltip: () =>
, - LineChart: ({ children }: any) =>
{children}
, Line: () =>
, -})); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -const saveTransitionArtifacts = (name: string, container: HTMLElement) => { - const artifactDir = 'tests/e2e/artifacts/compare/viewer'; - if (!fs.existsSync(artifactDir)) fs.mkdirSync(artifactDir, { recursive: true }); - fs.writeFileSync(path.join(artifactDir, `${name}.html`), container.innerHTML); -}; - -describe('Módulo Comparar - Viewer & A11y Final', () => { - beforeEach(() => { - vi.clearAllMocks(); - useComparisonStore.setState({ - compareItems: [{ productId: 'p1' }, { productId: 'p2' }], - compareCount: 2, compareIds: ['p1', 'p2'], isLoaded: true, - }); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('A11y: Valida integridade básica da página', async () => { - await renderPage(); - expect(await screen.findByText(/Comparador de Produtos/i)).toBeInTheDocument(); - }); - - it('Viewer: Gera snapshots DOM para o Auditor', async () => { - const { container } = await renderPage(); - saveTransitionArtifacts('audit-snapshot', container); - expect(fs.existsSync('tests/e2e/artifacts/compare/viewer/audit-snapshot.html')).toBe(true); - }); -}); diff --git a/tests/e2e/compare-visual.test.ts b/tests/e2e/compare-visual.test.ts deleted file mode 100644 index aa3c0f70a..000000000 --- a/tests/e2e/compare-visual.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from 'fs'; -import path from 'path'; - -/** - * Módulo: Comparar (E2E Avançado & Regressão Visual) - * Foco: Screenshots, Loading/Erro Radar & AI, Estabilidade de Transição. - */ - -test.describe("Módulo de Comparação - Visual Regression & Robustez", () => { - const evidenceDir = 'tests/e2e/evidence/advanced'; - - test.beforeAll(async () => { - if (!fs.existsSync(evidenceDir)) fs.mkdirSync(evidenceDir, { recursive: true }); - }); - - test.beforeEach(async ({ page }) => { - // Captura de logs para falhar em warnings do React - page.on('console', msg => { - if (msg.text().includes('update during render') || msg.text().includes('act(')) { - console.error(`[FAIL] React Warning: ${msg.text()}`); - } - }); - - // Estado inicial: 2 produtos - await page.goto("/produtos"); - const compareBtns = page.locator('button[aria-label*="comparar"], button:has-text("Comparar")'); - await compareBtns.nth(0).click(); - await compareBtns.nth(1).click(); - await page.goto("/comparar"); - - // Reduz animações para estabilidade visual - await page.addStyleTag({ content: '*, *::before, *::after { transition: none !important; animation: none !important; }' }); - }); - - test("Cenário 1: Visual Regression - Transições de Layout", async ({ page }) => { - // 1. Baseline: Modo Duelo (2 produtos) - await expect(page.locator("text=⚔️ Modo Duelo").or(page.locator("text=Ativar Modo Duelo"))).toBeVisible(); - await page.screenshot({ path: `${evidenceDir}/01-duel-mode-baseline.png` }); - - // 2. Transição: Adicionar 3º produto (Auto-switch para Galeria) - await page.goto("/produtos"); - await page.locator('button[aria-label*="comparar"], button:has-text("Comparar")').nth(2).click(); - await page.goto("/comparar"); - - await expect(page.locator("text=Galeria Visual")).toBeVisible(); - await page.screenshot({ path: `${evidenceDir}/02-gallery-view-3-products.png` }); - - // 3. Troca para Tabela Detalhada - await page.click("text=Tabela Detalhada"); - await expect(page.locator("table")).toBeVisible(); - await page.screenshot({ path: `${evidenceDir}/03-table-view-diff.png` }); - }); - - test("Cenário 2: Resiliência Radar Chart (Loading & Error)", async ({ page }) => { - // Mock de latência para testar Loading - await page.route('**/rpc/get_top_compared_products*', async route => { - await new Promise(resolve => setTimeout(resolve, 2000)); - await route.continue(); - }); - - await page.reload(); - // Verifica feedback de loading (skeleton ou spinner se houver) - // await expect(page.locator(".animate-pulse")).toBeVisible(); - - // Mock de erro de rede para Radar Chart / Fallback - await page.route('**/rpc/get_top_compared_products*', route => route.abort('failed')); - await page.reload(); - - // Deve exibir fallback sem quebrar - await expect(page.locator("text=Sugestões para começar").or(page.locator("text=Os mais comparados"))).toBeVisible(); - await page.screenshot({ path: `${evidenceDir}/04-radar-error-recovery.png` }); - }); - - test("Cenário 3: Advisor AI - Erro e Timeout", async ({ page }) => { - // Advisor AI geralmente depende de contexto de produtos. Mock de falha na IA. - await page.route('**/functions/v1/ai-advisor*', route => route.abort('timedout')); - - await expect(page.locator("text=Advisor AI").or(page.locator("text=Recomendação"))).toBeVisible(); - // Verifica se há mensagem de erro amigável ou se o componente lida com nulo - await page.screenshot({ path: `${evidenceDir}/05-ai-timeout-state.png` }); - }); - - test.afterEach(async ({ page }, testInfo) => { - if (testInfo.status !== testInfo.expectedStatus) { - // Gera dump do DOM em falha - const dom = await page.content(); - fs.writeFileSync(`${evidenceDir}/failure-${testInfo.title.replace(/\s+/g, '-')}.html`, dom); - } - }); -}); diff --git a/tests/e2e/compare-visual.test.tsx b/tests/e2e/compare-visual.test.tsx deleted file mode 100644 index 960ecb5df..000000000 --- a/tests/e2e/compare-visual.test.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import ComparePage from '../../src/pages/products/ComparePage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -// Mock window.scrollTo -window.scrollTo = vi.fn(); - -// Mocks consolidados para evitar warnings de contexto e hoisting -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false, startTour: vi.fn(), completeTour: vi.fn() }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), - order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), - maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }) }, - from: vi.fn().mockReturnValue(chain), - rpc: vi.fn().mockResolvedValue({ data: [] }), - }, - }; -}); - -const mockProducts = [ - { id: 'p1', name: 'Prod 1', price: 10, images: ['i1.jpg'], minQuantity: 1, stock: 10, stockStatus: 'in-stock', colors: [], sku: 'S1', category: { name: 'C1' }, supplier: { name: 'S1' } }, - { id: 'p2', name: 'Prod 2', price: 20, images: ['i2.jpg'], minQuantity: 2, stock: 20, stockStatus: 'in-stock', colors: [], sku: 'S2', category: { name: 'C1' }, supplier: { name: 'S1' } } -]; - -vi.mock('../../src/contexts/ProductsContext', () => ({ - useProductsContext: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - useProductsContextSafe: () => ({ products: mockProducts, getProductsByIds: (ids: string[]) => mockProducts.filter(p => ids.includes(p.id)) }), - ProductsContext: { Provider: ({ children }: any) =>
{children}
}, - ProductsProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('recharts', () => ({ - ResponsiveContainer: ({ children }: any) =>
{children}
, - Radar: () =>
, RadarChart: ({ children }: any) =>
{children}
, - PolarGrid: () =>
, PolarAngleAxis: () =>
, PolarRadiusAxis: () =>
, - Legend: () =>
, Tooltip: () =>
, - LineChart: ({ children }: any) =>
{children}
, Line: () =>
, -})); - -// Mock do Layout para simplificar renderização e evitar Suspense infinito em testes -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -describe('Módulo Comparar - Visual & Resiliência', () => { - beforeEach(() => { - vi.clearAllMocks(); - useComparisonStore.setState({ - compareItems: [{ productId: 'p1' }, { productId: 'p2' }], - compareCount: 2, compareIds: ['p1', 'p2'], isLoaded: true, - }); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Resiliência: Interface carrega sem erros de contexto', async () => { - await renderPage(); - expect(await screen.findByText(/Comparador de Produtos/i)).toBeInTheDocument(); - }); - - it('Transição: Detecta o Modo Duelo habilitado para exatamente 2 produtos', async () => { - await renderPage(); - // Verifica por texto que indica o estado (usando getAll pois o badge e o botão podem compartilhar o termo) - const elements = await screen.findAllByText(/Modo Duelo/i); - expect(elements.length).toBeGreaterThan(0); - }); - - it('Estabilidade: Gráficos de performance persistentes', async () => { - await renderPage(); - expect(screen.getByTestId('recharts-container')).toBeInTheDocument(); - }); - - it('Resiliência: Fallback visual ao limpar comparação', async () => { - await renderPage(); - const clearBtn = await screen.findByText(/Limpar/i); - fireEvent.click(clearBtn); - expect(await screen.findByText(/Selecione pelo menos 2 produtos/i)).toBeInTheDocument(); - }); -}); - diff --git a/tests/e2e/cross-cutting-regression.test.ts b/tests/e2e/cross-cutting-regression.test.ts deleted file mode 100644 index d86620659..000000000 --- a/tests/e2e/cross-cutting-regression.test.ts +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Cross-Cutting Concerns — Regression, Performance, Edge Cases, Integration - * Validates that changes don't break existing functionality and handles edge cases. - */ -import { describe, it, expect } from 'vitest'; - -// ============ Currency Formatting ============ -describe('Cross-Cutting — Currency Formatting', () => { - function formatCurrency(value: number): string { - return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); - } - - function formatNumber(value: number): string { - return new Intl.NumberFormat('pt-BR').format(value); - } - - it('formats BRL correctly', () => { - expect(formatCurrency(35.90)).toMatch(/R\$\s?35,90/); - }); - - it('formats zero', () => { - expect(formatCurrency(0)).toMatch(/R\$\s?0,00/); - }); - - it('formats large number', () => { - expect(formatCurrency(99999.99)).toMatch(/99\.999,99/); - }); - - it('formats negative', () => { - expect(formatCurrency(-10)).toMatch(/-R\$\s?10,00/); - }); - - it('formatNumber with thousands', () => { - expect(formatNumber(1500)).toBe('1.500'); - }); - - it('formatNumber small', () => { - expect(formatNumber(42)).toBe('42'); - }); -}); - -// ============ Regression: Wizard Steps ============ -describe('Cross-Cutting — Wizard Step Regression', () => { - type Step = 'product' | 'techniques' | 'results'; - const STEPS: Step[] = ['product', 'techniques', 'results']; - - it('wizard still has 3 base steps', () => { - expect(STEPS).toHaveLength(3); - }); - - it('product step first', () => { - expect(STEPS[0]).toBe('product'); - }); - - it('results step last', () => { - expect(STEPS[STEPS.length - 1]).toBe('results'); - }); -}); - -// ============ Regression: Price Tiers ============ -describe('Cross-Cutting — Price Tier Regression', () => { - interface PriceTier { minQty: number; maxQty: number; unitPrice: number; } - - const tiers: PriceTier[] = [ - { minQty: 1, maxQty: 49, unitPrice: 12.00 }, - { minQty: 50, maxQty: 99, unitPrice: 10.50 }, - { minQty: 100, maxQty: 249, unitPrice: 8.90 }, - { minQty: 250, maxQty: 499, unitPrice: 7.50 }, - { minQty: 500, maxQty: 999, unitPrice: 6.00 }, - { minQty: 1000, maxQty: Infinity, unitPrice: 5.00 }, - ]; - - function getPrice(qty: number): number { - const tier = tiers.find(t => qty >= t.minQty && qty <= t.maxQty); - return tier?.unitPrice ?? tiers[0].unitPrice; - } - - it('1 unit = R$12', () => expect(getPrice(1)).toBe(12)); - it('50 units = R$10.50', () => expect(getPrice(50)).toBe(10.50)); - it('100 units = R$8.90', () => expect(getPrice(100)).toBe(8.90)); - it('250 units = R$7.50', () => expect(getPrice(250)).toBe(7.50)); - it('500 units = R$6.00', () => expect(getPrice(500)).toBe(6.00)); - it('1000 units = R$5.00', () => expect(getPrice(1000)).toBe(5.00)); - it('boundary: 49 = R$12', () => expect(getPrice(49)).toBe(12)); - it('boundary: 99 = R$10.50', () => expect(getPrice(99)).toBe(10.50)); - it('boundary: 999 = R$6.00', () => expect(getPrice(999)).toBe(6.00)); - it('10000 units = R$5.00', () => expect(getPrice(10000)).toBe(5.00)); -}); - -// ============ Edge Cases: Size Ordering ============ -describe('Cross-Cutting — Size Ordering Edge Cases', () => { - const SIZE_ORDER = ['PP', 'P', 'M', 'G', 'GG', 'XG', 'XXG', 'EG', 'EGG']; - - function sizeSort(a: string, b: string): number { - const ia = SIZE_ORDER.indexOf(a.toUpperCase()); - const ib = SIZE_ORDER.indexOf(b.toUpperCase()); - if (ia !== -1 && ib !== -1) return ia - ib; - if (ia !== -1) return -1; - if (ib !== -1) return 1; - return a.localeCompare(b, 'pt-BR'); - } - - it('empty array stays empty', () => { - expect([].sort(sizeSort)).toEqual([]); - }); - - it('single element', () => { - expect(['M'].sort(sizeSort)).toEqual(['M']); - }); - - it('all standard sizes', () => { - const shuffled = ['GG', 'PP', 'G', 'M', 'P', 'XG']; - expect(shuffled.sort(sizeSort)).toEqual(['PP', 'P', 'M', 'G', 'GG', 'XG']); - }); - - it('duplicate sizes', () => { - const dupes = ['M', 'G', 'M', 'G']; - dupes.sort(sizeSort); - expect(dupes[0]).toBe('M'); - expect(dupes[1]).toBe('M'); - expect(dupes[2]).toBe('G'); - }); - - it('unknown sizes sorted alphabetically', () => { - const custom = ['Zebra', 'Alfa', 'Beta']; - expect(custom.sort(sizeSort)).toEqual(['Alfa', 'Beta', 'Zebra']); - }); - - it('mix of known and unknown', () => { - const mixed = ['Custom', 'M', 'G', 'Especial']; - const sorted = mixed.sort(sizeSort); - expect(sorted[0]).toBe('M'); - expect(sorted[1]).toBe('G'); - // Custom and Especial after known sizes - }); - - it('handles case mismatch', () => { - expect(SIZE_ORDER.indexOf('M')).toBeGreaterThanOrEqual(0); - expect(SIZE_ORDER.indexOf('m')).toBe(-1); - expect(SIZE_ORDER.indexOf('m'.toUpperCase())).toBeGreaterThanOrEqual(0); - }); -}); - -// ============ Edge Cases: Color Hex Validation ============ -describe('Cross-Cutting — Color Hex Edge Cases', () => { - function isValidHex(hex: string): boolean { - return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(hex); - } - - it('valid 6-char hex', () => expect(isValidHex('#FF0000')).toBe(true)); - it('valid 3-char hex', () => expect(isValidHex('#FFF')).toBe(true)); - it('lowercase hex', () => expect(isValidHex('#abcdef')).toBe(true)); - it('mixed case hex', () => expect(isValidHex('#AbCdEf')).toBe(true)); - it('invalid: no hash', () => expect(isValidHex('FF0000')).toBe(false)); - it('invalid: 4 chars', () => expect(isValidHex('#FFFF')).toBe(false)); - it('invalid: special chars', () => expect(isValidHex('#GGGGGG')).toBe(false)); - it('invalid: empty', () => expect(isValidHex('')).toBe(false)); -}); - -// ============ Integration: Product → Variant → Grid ============ -describe('Cross-Cutting — Full Product Variant Pipeline', () => { - it('product with no variations shows no grid', () => { - const product = { id: '1', name: 'Adesivo', variations: [] }; - expect(product.variations.length === 0).toBe(true); - }); - - it('product with colors only shows flat list', () => { - const product = { - id: '2', name: 'Caneta', - variations: [ - { id: 'v1', color_name: 'Azul', color_hex: '#00F', size_code: null }, - { id: 'v2', color_name: 'Preto', color_hex: '#000', size_code: null }, - ], - }; - const hasSizes = product.variations.some(v => v.size_code); - expect(hasSizes).toBe(false); - }); - - it('product with colors + sizes shows grid matrix', () => { - const product = { - id: '3', name: 'Camiseta', - variations: [ - { id: 'v1', color_name: 'Preto', color_hex: '#000', size_code: 'P', stock: 50 }, - { id: 'v2', color_name: 'Preto', color_hex: '#000', size_code: 'M', stock: 100 }, - { id: 'v3', color_name: 'Azul', color_hex: '#00F', size_code: 'P', stock: 30 }, - { id: 'v4', color_name: 'Azul', color_hex: '#00F', size_code: 'M', stock: 80 }, - ], - }; - const hasSizes = product.variations.some(v => v.size_code); - expect(hasSizes).toBe(true); - - const colors = [...new Set(product.variations.map(v => v.color_name))]; - expect(colors).toHaveLength(2); - - const sizes = [...new Set(product.variations.filter(v => v.size_code).map(v => v.size_code))]; - expect(sizes).toHaveLength(2); - }); - - it('product with gender shows badge', () => { - const product = { name: 'Polo', gender: 'masculino' }; - const GENDER_CONFIG: Record = { - masculino: 'Masc.', feminino: 'Fem.', infantil: 'Infantil', unissex: 'Unissex', - }; - const label = GENDER_CONFIG[product.gender]; - expect(label).toBe('Masc.'); - }); - - it('product without gender shows no badge', () => { - const product = { name: 'Squeeze', gender: null as string | null }; - const GENDER_CONFIG: Record = { - masculino: 'Masc.', feminino: 'Fem.', - }; - const label = product.gender ? GENDER_CONFIG[product.gender] : null; - expect(label).toBeNull(); - }); -}); - -// ============ Data Consistency ============ -describe('Cross-Cutting — Data Consistency', () => { - it('size_code exists in quote_items schema', () => { - // quote_items table has size_code field - const quoteItemColumns = [ - 'id', 'quote_id', 'product_name', 'product_sku', 'quantity', - 'unit_price', 'color_name', 'color_hex', 'size_code', 'subtotal', - ]; - expect(quoteItemColumns).toContain('size_code'); - }); - - it('KitItem has selectedSize field', () => { - interface KitItem { - id: string; - selectedSize?: string; - } - const item: KitItem = { id: '1', selectedSize: 'M' }; - expect(item.selectedSize).toBe('M'); - }); - - it('VariantSelectionData has size field', () => { - interface VariantSelectionData { - color: { name: string }; - size?: string; - } - const data: VariantSelectionData = { color: { name: 'Preto' }, size: 'G' }; - expect(data.size).toBe('G'); - }); - - it('ProductVariant has size_code and sale_price', () => { - interface ProductVariant { - code: string; - name: string; - size_code?: string | null; - sale_price?: number | null; - } - const v: ProductVariant = { code: 'v1', name: 'Preto', size_code: 'M', sale_price: 35.90 }; - expect(v.size_code).toBe('M'); - expect(v.sale_price).toBe(35.90); - }); -}); - -// ============ Performance: Large Dataset Handling ============ -describe('Cross-Cutting — Performance with large datasets', () => { - it('handles 1000 variants efficiently', () => { - const start = performance.now(); - const variants = Array.from({ length: 1000 }, (_, i) => ({ - id: `v${i}`, - color_name: `Color${i % 20}`, - color_hex: '#000000', - size_code: ['PP', 'P', 'M', 'G', 'GG'][i % 5], - stock: Math.floor(Math.random() * 500), - })); - - // Build grouped structure - const grouped = new Map(); - variants.forEach(v => { - if (!grouped.has(v.color_name)) grouped.set(v.color_name, []); - grouped.get(v.color_name)!.push(v); - }); - - const elapsed = performance.now() - start; - expect(grouped.size).toBe(20); - expect(elapsed).toBeLessThan(100); // Should complete in < 100ms - }); - - it('handles 5000 variants for size extraction', () => { - const start = performance.now(); - const variants = Array.from({ length: 5000 }, (_, i) => ({ - size_code: ['PP', 'P', 'M', 'G', 'GG', 'XG', '36', '38', '40', '42'][i % 10], - })); - - const sizeSet = new Set(); - variants.forEach(v => { if (v.size_code) sizeSet.add(v.size_code); }); - - const elapsed = performance.now() - start; - expect(sizeSet.size).toBe(10); - expect(elapsed).toBeLessThan(50); - }); - - it('handles 200 products for gender filter', () => { - const start = performance.now(); - const products = Array.from({ length: 200 }, (_, i) => ({ - id: `p${i}`, - gender: ['masculino', 'feminino', 'unissex', 'infantil', null][i % 5], - })); - - const filtered = products.filter(p => p.gender === 'masculino'); - const elapsed = performance.now() - start; - - expect(filtered).toHaveLength(40); - expect(elapsed).toBeLessThan(10); - }); - - it('cartesian product 20 colors × 10 sizes = 200 cells', () => { - const colors = Array.from({ length: 20 }, (_, i) => `Color${i}`); - const sizes = Array.from({ length: 10 }, (_, i) => `Size${i}`); - - const start = performance.now(); - const cells: string[] = []; - colors.forEach(c => sizes.forEach(s => cells.push(`${c}-${s}`))); - const elapsed = performance.now() - start; - - expect(cells).toHaveLength(200); - expect(elapsed).toBeLessThan(10); - }); -}); diff --git a/tests/e2e/favorites-module.test.ts b/tests/e2e/favorites-module.test.ts deleted file mode 100644 index 35eae0a12..000000000 --- a/tests/e2e/favorites-module.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * E2E Tests — Favorites Module (Meus Favoritos) - * Covers: Sidebar, Lists (CRUD), Sharing, Export, Search, Trash, Bulk Actions, Shortcuts, Accessibility, Pagination & Sorting - */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; - -// ============ Data Types ============ -interface FavoriteList { - id: string; - name: string; - color: string; - icon: string; - item_count: number; - is_default: boolean; - shared_token?: string | null; - shared_expires_at?: string | null; -} - -interface FavoriteItem { - id: string; - product_id: string; - product_name: string; - added_at: string; - note: string | null; - price_at_save: number; - current_price: number; -} - -// ============ Mock Data ============ -const mockLists: FavoriteList[] = [ - { id: 'list-default', name: 'Lista Geral', color: '#EF4444', icon: 'heart', item_count: 5, is_default: true }, - { id: 'list-client-a', name: 'Evento Cliente A', color: '#3B82F6', icon: 'star', item_count: 12, is_default: false, shared_token: 'token123', shared_expires_at: '2026-06-01T00:00:00Z' }, - { id: 'list-new-collection', name: 'Inverno 2026', color: '#10B981', icon: 'package', item_count: 0, is_default: false }, -]; - -const mockItems: FavoriteItem[] = [ - { id: 'item-1', product_id: 'p1', product_name: 'Squeeze Térmico 500ml', added_at: '2026-05-01T10:00:00Z', note: 'Brinde para diretoria', price_at_save: 35.00, current_price: 35.00 }, - { id: 'item-2', product_id: 'p2', product_name: 'Mochila Executiva Nylon', added_at: '2026-05-01T11:00:00Z', note: null, price_at_save: 120.00, current_price: 110.00 }, // Price drop! - { id: 'item-3', product_id: 'p3', product_name: 'Caneta Metal Premium', added_at: '2026-05-02T09:00:00Z', note: 'Gravação laser', price_at_save: 12.50, current_price: 12.50 }, -]; - -// ============ Business Logic Tests ============ - -describe('E2E Favoritos — Gestão de Listas', () => { - it('identifica a lista padrão corretamente', () => { - const defaultList = mockLists.find(l => l.is_default); - expect(defaultList?.id).toBe('list-default'); - }); - - it('valida token de compartilhamento ativo', () => { - const sharedList = mockLists.find(l => l.shared_token); - expect(sharedList?.shared_token).toBeDefined(); - expect(sharedList?.name).toBe('Evento Cliente A'); - }); - - it('calcula total de itens entre todas as listas', () => { - const total = mockLists.reduce((acc, l) => acc + l.item_count, 0); - expect(total).toBe(17); - }); -}); - -describe('E2E Favoritos — Compartilhamento Completo', () => { - it('valida expiração de token', () => { - const expiredTokenDate = '2026-04-01T00:00:00Z'; // Past date - const now = new Date('2026-05-03T00:00:00Z').getTime(); - const expiry = new Date(expiredTokenDate).getTime(); - expect(expiry < now).toBe(true); - }); - - it('revogação de token limpa dados de compartilhamento', () => { - const list = { ...mockLists[1] }; - list.shared_token = null; - list.shared_expires_at = null; - expect(list.shared_token).toBeNull(); - expect(list.shared_expires_at).toBeNull(); - }); - - it('acesso anônimo visualiza apenas dados públicos da lista', () => { - const publicData = { - name: mockLists[1].name, - items: mockItems.map(it => ({ name: it.product_name, price: it.current_price })) - }; - expect(publicData.name).toBe('Evento Cliente A'); - expect(publicData.items).toHaveLength(3); - // Anônimo não deve ver notas privadas ou ID do usuário se o sistema for bem desenhado - expect((publicData as any).user_id).toBeUndefined(); - }); -}); - -describe('E2E Favoritos — Paginação, Ordenação e Filtros', () => { - it('filtra por busca textual (Nome ou Nota)', () => { - const query = 'térmico'; - const filtered = mockItems.filter(it => - it.product_name.toLowerCase().includes(query) || - (it.note?.toLowerCase().includes(query)) - ); - expect(filtered).toHaveLength(1); - }); - - it('ordena por preço ascendente', () => { - const sorted = [...mockItems].sort((a, b) => a.current_price - b.current_price); - expect(sorted[0].product_id).toBe('p3'); // Caneta (12.50) - expect(sorted[2].product_id).toBe('p2'); // Mochila (110.00) - }); - - it('gerencia paginação (Exemplo de lógica)', () => { - const PAGE_SIZE = 2; - const page1 = mockItems.slice(0, PAGE_SIZE); - const page2 = mockItems.slice(PAGE_SIZE, PAGE_SIZE * 2); - expect(page1).toHaveLength(2); - expect(page2).toHaveLength(1); - }); - - it('trata estado de busca vazia', () => { - const filtered = mockItems.filter(it => it.product_name.includes('XYZ-Nenhum-Match')); - expect(filtered).toHaveLength(0); - // UI deve mostrar EmptyState - }); -}); - -describe('E2E Favoritos — Fluxo de Lixeira', () => { - const mockTrash = [ - { id: 't1', product_id: 'p99', product_name: 'Item Deletado', deleted_at: '2026-05-01' } - ]; - - it('restaura item da lixeira para a lista original', () => { - const item = mockTrash[0]; - const restoredItem = { ...item, list_id: 'list-default' }; - expect(restoredItem.list_id).toBe('list-default'); - // Em produção, isso dispararia o toast de sucesso e recarregaria os dados - }); - - it('apaga definitivamente limpa o item da lixeira', () => { - const trashAfterPurge = mockTrash.filter(t => t.id !== 't1'); - expect(trashAfterPurge).toHaveLength(0); - }); - - it('recuperação após recarregar a página (Persistência)', () => { - // Simula que os dados persistem no backend/cache - const stateBeforeReload = { trashCount: 1 }; - const stateAfterReload = { trashCount: 1 }; // Mantém integridade - expect(stateAfterReload.trashCount).toBe(stateBeforeReload.trashCount); - }); -}); - -describe('E2E Favoritos — Acessibilidade', () => { - it('valida foco visível e navegação por teclado', () => { - // Simulação de atributos ARIA e foco - const button = { - role: 'button', - 'aria-label': 'Nova lista', - tabIndex: 0, - className: 'focus-visible:ring-2' - }; - expect(button.tabIndex).toBe(0); - expect(button['aria-label']).toBe('Nova lista'); - expect(button.className).toContain('focus-visible'); - }); - - it('garante rótulos em campos de busca', () => { - const input = { - placeholder: 'Buscar nos favoritos...', - 'aria-label': 'Busca de favoritos' - }; - expect(input['aria-label']).toBe('Busca de favoritos'); - }); -}); - -describe('E2E Favoritos — Exportação', () => { - it('gera nome de arquivo seguro e normalizado', () => { - const listName = 'Minha Lista de Verão!'; - const safeName = listName.toLowerCase() - .normalize("NFD").replace(/[\u0300-\u036f]/g, "") - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-|-$/g, ""); - expect(safeName).toBe('minha-lista-de-verao'); - }); -}); - diff --git a/tests/e2e/favorites-ui.test.tsx b/tests/e2e/favorites-ui.test.tsx deleted file mode 100644 index eb45229bc..000000000 --- a/tests/e2e/favorites-ui.test.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { BrowserRouter, useParams } from 'react-router-dom'; -import FavoritesPage from '@/pages/FavoritesPage'; -import PublicFavoriteListPage from '@/pages/PublicFavoriteListPage'; -import { useFavoritesStore } from '@/stores/useFavoritesStore'; -import { useAuth } from '@/contexts/AuthContext'; -import { supabase } from '@/integrations/supabase/client'; -import { Toaster } from 'sonner'; -import { ProductsContext } from '@/contexts/ProductsContext'; -import { HelmetProvider } from 'react-helmet-async'; -import { TooltipProvider } from '@/components/ui/tooltip'; -import { AriaLiveProvider } from '@/components/a11y/AriaLive'; - -// Mocks -vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useParams: vi.fn(), - }; -}); - -vi.mock('@/contexts/AuthContext', () => ({ - useAuth: vi.fn(), -})); - -vi.mock('@/stores/useFavoritesStore', () => ({ - useFavoritesStore: vi.fn(), -})); - -vi.mock('@/integrations/supabase/client', () => ({ - supabase: { - from: vi.fn().mockImplementation(() => ({ - select: vi.fn().mockReturnThis(), - update: vi.fn().mockReturnThis(), - delete: vi.fn().mockReturnThis(), - insert: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - order: vi.fn().mockReturnThis(), - single: vi.fn().mockResolvedValue({ data: null, error: null }), - maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }), - in: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), - })), - rpc: vi.fn().mockResolvedValue({ data: null, error: null }), - functions: { - invoke: vi.fn().mockResolvedValue({ data: { products: [] }, error: null }), - }, - auth: { - getSession: vi.fn().mockResolvedValue({ data: { session: null }, error: null }), - }, - }, -})); - -vi.mock('@/hooks/ui/useOnboarding', () => ({ - useOnboarding: () => ({ - step: null, - isFirstTime: false, - complete: vi.fn(), - reset: vi.fn(), - }), -})); - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { retry: false }, - }, -}); - -const mockProductsContext = { - products: [], - isLoading: false, - getProductById: vi.fn(), - getProductsByIds: vi.fn().mockReturnValue([]), - registerProducts: vi.fn(), -}; - -const renderWithProviders = (ui: React.ReactElement) => { - return render( - - - - - - - - {ui} - - - - - - - ); -}; - -describe('E2E Favoritos — Integração UI', () => { - beforeEach(() => { - vi.clearAllMocks(); - (useAuth as any).mockReturnValue({ user: { id: 'user123' } }); - (useFavoritesStore as any).mockReturnValue({ - favorites: [], - favoriteCount: 0, - clearFavorites: vi.fn(), - toggleFavorite: vi.fn(), - isFavorite: vi.fn().mockReturnValue(false), - }); - (useParams as any).mockReturnValue({}); - }); - - it('valida título da página e contador inicial', async () => { - renderWithProviders(); - expect(screen.getByTestId('page-title-favoritos')).toHaveTextContent('Meus Favoritos'); - expect(screen.getByTestId('favorites-count-items')).toHaveTextContent('0'); - }); - - it('exibe Empty State quando não há produtos', async () => { - renderWithProviders(); - await waitFor(() => { - expect(screen.getByTestId('favorites-empty-state')).toBeInTheDocument(); - }); - expect(screen.getByTestId('favorites-empty-cta')).toBeInTheDocument(); - }); -}); - -describe('E2E Favoritos — Fluxo de Compartilhamento UI', () => { - it('valida renderização da mensagem de lista não encontrada', async () => { - (useParams as any).mockReturnValue({ token: 'invalid-token' }); - (supabase.from as any).mockImplementation(() => ({ - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }), - })); - - renderWithProviders(); - await waitFor(() => { - expect(screen.getByText(/lista não encontrada/i)).toBeInTheDocument(); - }); - }); - - it('exibe mensagem de link expirado corretamente quando o token existe mas shared_expires_at é passado', async () => { - const expiredDate = new Date(Date.now() - 86400000).toISOString(); - (useParams as any).mockReturnValue({ token: 'expired-token' }); - - (supabase.from as any).mockImplementation((table: string) => { - if (table === 'favorite_lists') { - return { - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - maybeSingle: vi.fn().mockResolvedValue({ - data: { - id: 'list1', - name: 'Lista Expira', - shared_expires_at: expiredDate, - color: '#000000', - description: 'Desc' - }, - error: null - }), - }; - } - return { - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - order: vi.fn().mockReturnThis(), - }; - }); - - renderWithProviders(); - await waitFor(() => { - expect(screen.getByText(/link expirado/i)).toBeInTheDocument(); - }, { timeout: 3000 }); - }); -}); - -describe('E2E Favoritos — Acessibilidade UI', () => { - it('garante que botões principais tenham labels acessíveis via aria-label', () => { - renderWithProviders(); - expect(screen.getByLabelText('Favoritos')).toBeInTheDocument(); - }); -}); diff --git a/tests/e2e/infra-banners-prod.test.tsx b/tests/e2e/infra-banners-prod.test.tsx deleted file mode 100644 index 6d42205d7..000000000 --- a/tests/e2e/infra-banners-prod.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, act } from '../test-utils'; -import { CloudStatusBanner } from '@/components/system/CloudStatusBanner'; -import { BridgeStatusBanner } from '@/components/BridgeStatusBanner'; -import { emitBridgeStatus } from '@/lib/external-db/bridge-status-events'; -import { toast } from 'sonner'; - -// Mocks -vi.mock('sonner', () => ({ - toast: { - loading: vi.fn(), - error: vi.fn(), - success: vi.fn(), - dismiss: vi.fn(), - }, -})); - -const mockUseAuth = vi.fn(); -vi.mock('@/contexts/AuthContext', () => ({ - useAuth: () => mockUseAuth(), -})); - -const mockUseCloudStatus = vi.fn(); -vi.mock('@/hooks/useCloudStatus', () => ({ - useCloudStatus: () => mockUseCloudStatus(), -})); - -// Helper para simular ambiente de PROD (gate fechado para não-devs) -const setupProdNonDev = () => { - mockUseAuth.mockReturnValue({ isDev: false }); - vi.stubEnv('VITE_SHOW_DEV_INFRA_MESSAGES', 'false'); - // Limpar localStorage para garantir que não há overrides - if (typeof window !== 'undefined') { - window.localStorage.removeItem('show_dev_infra_messages'); - } -}; - -describe('E2E Infra Banners — Modo PROD (Usuário Não-Dev)', () => { - beforeEach(() => { - vi.clearAllMocks(); - setupProdNonDev(); - }); - - describe('CloudStatusBanner', () => { - it('OCULTA mensagem técnica "warming" (reiniciando)', () => { - mockUseCloudStatus.mockReturnValue({ - status: 'warming', - retry: vi.fn(), - isChecking: false - }); - - const { container } = render(); - expect(container).toBeEmptyDOMElement(); - }); - - it('EXIBE mensagem crítica "down" (indisponível)', () => { - mockUseCloudStatus.mockReturnValue({ - status: 'down', - retry: vi.fn(), - isChecking: false - }); - - render(); - expect(screen.getByText(/Backend indisponível/i)).toBeInTheDocument(); - // Não deve ter a classe de spin (que é do warming) - const icon = screen.getByRole('status').querySelector('svg'); - expect(icon).not.toHaveClass('animate-spin'); - }); - - it('EXIBE mensagem de instabilidade "degraded"', () => { - mockUseCloudStatus.mockReturnValue({ - status: 'degraded', - retry: vi.fn(), - isChecking: false - }); - - render(); - expect(screen.getByText(/Backend instável/i)).toBeInTheDocument(); - }); - }); - - describe('BridgeStatusBanner', () => { - it('OCULTA toast técnico de "degraded" (reconectando)', () => { - render(); - - act(() => { - emitBridgeStatus({ - type: 'degraded', - attempt: 1, - maxAttempts: 3, - delayMs: 1000, - reason: '503 cold start' - }); - }); - - expect(toast.loading).not.toHaveBeenCalled(); - }); - - it('EXIBE banner crítico "unavailable" com texto amigável (não técnico)', () => { - render(); - - act(() => { - emitBridgeStatus({ - type: 'unavailable', - reason: 'Circuit breaker open', - attempts: 5 - }); - }); - - // Verifica o banner sticky - expect(screen.getByRole('alert')).toBeInTheDocument(); - expect(screen.getByText(/Catálogo temporariamente indisponível/i)).toBeInTheDocument(); - - // Verifica que NÃO usa a mensagem técnica reservada para devs - expect(screen.queryByText(/Tentativas automáticas esgotadas/i)).not.toBeInTheDocument(); - expect(screen.getByText(/Estamos com uma instabilidade momentânea no catálogo/i)).toBeInTheDocument(); - - // Verifica o toast de erro - expect(toast.error).toHaveBeenCalledWith( - 'Catálogo temporariamente indisponível', - expect.objectContaining({ - description: expect.stringContaining('Estamos com uma instabilidade momentânea no acesso ao catálogo') - }) - ); - }); - - it('PERMITE fechar o aviso mesmo em modo crítico', async () => { - render(); - - act(() => { - emitBridgeStatus({ type: 'unavailable', reason: 'fail', attempts: 1 }); - }); - - const closeButton = screen.getByLabelText(/Fechar aviso/i); - act(() => { - closeButton.click(); - }); - - expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - }); - }); -}); diff --git a/tests/e2e/kit-builder-flow.test.ts b/tests/e2e/kit-builder-flow.test.ts deleted file mode 100644 index 3592e6520..000000000 --- a/tests/e2e/kit-builder-flow.test.ts +++ /dev/null @@ -1,374 +0,0 @@ -/** - * Comprehensive E2E tests for Kit Builder flows - * Covers full user journeys through the kit assembly wizard - */ -import { describe, it, expect, vi } from "vitest"; - -// These are behavioral/logic tests simulating full kit builder flows - -describe("Kit Builder E2E Flow - Box Selection", () => { - it("validates box selection is required before adding items", () => { - const selectedBox = null; - const canAddItem = selectedBox !== null; - expect(canAddItem).toBe(false); - }); - - it("allows item addition after box selection", () => { - const selectedBox = { id: "b1", name: "Box", internalVolume: 1000000 }; - const canAddItem = selectedBox !== null; - expect(canAddItem).toBe(true); - }); - - it("prevents proceeding to items step without box", () => { - const currentStep = "box"; - const selectedBox = null; - const canProceed = currentStep === "box" && selectedBox !== null; - expect(canProceed).toBe(false); - }); -}); - -describe("Kit Builder E2E Flow - Items Management", () => { - it("allows adding multiple items", () => { - const items = [ - { id: "1", name: "Caneta", quantity: 1 }, - { id: "2", name: "Caderno", quantity: 2 }, - ]; - expect(items.length).toBe(2); - expect(items.reduce((sum, i) => sum + i.quantity, 0)).toBe(3); - }); - - it("prevents duplicate items (increases quantity instead)", () => { - const items = [{ id: "1", name: "Caneta", quantity: 1 }]; - const newItem = { id: "1", name: "Caneta", quantity: 1 }; - const existingIndex = items.findIndex(i => i.id === newItem.id); - expect(existingIndex).toBe(0); - if (existingIndex >= 0) { - items[existingIndex].quantity += 1; - } - expect(items[0].quantity).toBe(2); - expect(items.length).toBe(1); - }); - - it("removes item when quantity reaches 0", () => { - let items = [{ id: "1", name: "Caneta", quantity: 1 }]; - const newQuantity = 0; - if (newQuantity <= 0) { - items = items.filter(i => i.id !== "1"); - } - expect(items.length).toBe(0); - }); - - it("reorders items correctly", () => { - const items = [ - { id: "1", name: "A" }, - { id: "2", name: "B" }, - { id: "3", name: "C" }, - ]; - const [moved] = items.splice(0, 1); - items.splice(2, 0, moved); - expect(items.map(i => i.name)).toEqual(["B", "C", "A"]); - }); -}); - -describe("Kit Builder E2E Flow - Volume Validation", () => { - it("blocks items that exceed box volume", () => { - const boxVolume = 1000; - const usedVolume = 900; - const newItemVolume = 200; - const fits = (usedVolume + newItemVolume) <= boxVolume; - expect(fits).toBe(false); - }); - - it("allows items within capacity", () => { - const boxVolume = 1000; - const usedVolume = 500; - const newItemVolume = 200; - const fits = (usedVolume + newItemVolume) <= boxVolume; - expect(fits).toBe(true); - }); - - it("calculates usage percent correctly", () => { - const used = 750; - const total = 1000; - const percent = (used / total) * 100; - expect(percent).toBe(75); - }); - - it("detects near-capacity state (>80%)", () => { - const percent = 85; - expect(percent > 80 && percent <= 100).toBe(true); - }); - - it("detects over-capacity state (>100%)", () => { - const percent = 110; - expect(percent > 100).toBe(true); - }); -}); - -describe("Kit Builder E2E Flow - Pricing", () => { - it("calculates total with box + items + personalization", () => { - const boxPrice = 10; - const itemsPrice = 25; - const personalizationPrice = 5; - const quantity = 10; - const unitPrice = boxPrice + itemsPrice + personalizationPrice; - const total = unitPrice * quantity; - expect(unitPrice).toBe(40); - expect(total).toBe(400); - }); - - it("calculates markup correctly", () => { - const costPrice = 40; - const markupPercent = 30; - const sellingPrice = costPrice * (1 + markupPercent / 100); - expect(sellingPrice).toBeCloseTo(52); - const margin = ((sellingPrice - costPrice) / sellingPrice) * 100; - expect(margin).toBeCloseTo(23.08, 1); - }); - - it("handles zero markup", () => { - const costPrice = 40; - const markupPercent = 0; - const sellingPrice = costPrice * (1 + markupPercent / 100); - expect(sellingPrice).toBe(40); - }); - - it("handles 100% markup", () => { - const costPrice = 40; - const markupPercent = 100; - const sellingPrice = costPrice * (1 + markupPercent / 100); - expect(sellingPrice).toBe(80); - const margin = ((sellingPrice - costPrice) / sellingPrice) * 100; - expect(margin).toBe(50); - }); -}); - -describe("Kit Builder E2E Flow - Weight Validation", () => { - it("calculates total weight including box", () => { - const boxWeight = 200; - const items = [ - { weight: 25, quantity: 2 }, - { weight: 200, quantity: 1 }, - ]; - const itemsWeight = items.reduce((sum, i) => sum + (i.weight * i.quantity), 0); - const totalWeight = boxWeight + itemsWeight; - expect(itemsWeight).toBe(250); - expect(totalWeight).toBe(450); - }); - - it("validates max weight limit", () => { - const maxWeight = 5000; - const itemsWeight = 4500; - expect(itemsWeight <= maxWeight).toBe(true); - expect(6000 <= maxWeight).toBe(false); - }); -}); - -describe("Kit Builder E2E Flow - Wizard Navigation", () => { - const steps = ["box", "items", "personalization", "summary"]; - - it("navigates forward through steps", () => { - let currentIndex = 0; - currentIndex++; - expect(steps[currentIndex]).toBe("items"); - currentIndex++; - expect(steps[currentIndex]).toBe("personalization"); - currentIndex++; - expect(steps[currentIndex]).toBe("summary"); - }); - - it("navigates backward through steps", () => { - let currentIndex = 3; - currentIndex--; - expect(steps[currentIndex]).toBe("personalization"); - }); - - it("cannot go before first step", () => { - let currentIndex = 0; - if (currentIndex > 0) currentIndex--; - expect(currentIndex).toBe(0); - }); - - it("cannot go past last step", () => { - let currentIndex = 3; - if (currentIndex < steps.length - 1) currentIndex++; - expect(currentIndex).toBe(3); - }); - - it("tracks completed steps", () => { - const completed: string[] = []; - const hasBox = true; - const hasItems = true; - const hasPersonalization = false; - - if (hasBox) completed.push("box"); - if (hasItems) completed.push("items"); - if (hasPersonalization) completed.push("personalization"); - - expect(completed).toEqual(["box", "items"]); - }); -}); - -describe("Kit Builder E2E Flow - Personalization", () => { - it("enables box personalization", () => { - const personalization = { box: { enabled: false }, items: {} }; - personalization.box.enabled = true; - expect(personalization.box.enabled).toBe(true); - }); - - it("sets item personalization", () => { - const personalization: any = { box: { enabled: false }, items: {} }; - personalization.items["item-1"] = { enabled: true, technique: "laser", colors: 1 }; - expect(personalization.items["item-1"].technique).toBe("laser"); - }); - - it("removes personalization when item is removed", () => { - const personalization: any = { - box: { enabled: false }, - items: { "item-1": { enabled: true }, "item-2": { enabled: true } }, - }; - const { "item-1": _, ...rest } = personalization.items; - personalization.items = rest; - expect(Object.keys(personalization.items)).toEqual(["item-2"]); - }); -}); - -describe("Kit Builder E2E Flow - Kit Persistence", () => { - it("validates kit before saving", () => { - const errors: string[] = []; - const box = null; - const items: any[] = []; - - if (!box) errors.push("Selecione uma caixa"); - if (items.length === 0) errors.push("Adicione pelo menos um item"); - - expect(errors).toHaveLength(2); - expect(errors[0]).toContain("caixa"); - }); - - it("valid kit passes validation", () => { - const errors: string[] = []; - const box = { id: "b1" }; - const items = [{ id: "i1" }]; - const volumePercent = 50; - - if (!box) errors.push("Selecione uma caixa"); - if (items.length === 0) errors.push("Adicione pelo menos um item"); - if (volumePercent > 100) errors.push("Volume excedido"); - - expect(errors).toHaveLength(0); - }); -}); - -describe("Kit Builder E2E Flow - Freight Estimation", () => { - const freightTable = { - sedex: [ - { maxKg: 1, price: 22.50 }, - { maxKg: 5, price: 35.00 }, - { maxKg: 10, price: 55.00 }, - { maxKg: 30, price: 95.00 }, - { maxKg: Infinity, price: 150.00 }, - ], - }; - - it("selects correct price tier for weight", () => { - const weightKg = 3; - const tier = freightTable.sedex.find(t => weightKg <= t.maxKg); - expect(tier?.price).toBe(35.00); - }); - - it("handles very light packages", () => { - const weightKg = 0.5; - const tier = freightTable.sedex.find(t => weightKg <= t.maxKg); - expect(tier?.price).toBe(22.50); - }); - - it("handles heavy packages", () => { - const weightKg = 50; - const tier = freightTable.sedex.find(t => weightKg <= t.maxKg); - expect(tier?.price).toBe(150.00); - }); - - it("calculates per-kit freight cost", () => { - const freightPerShipment = 35.00; - const kitsPerShipment = 5; - const perKit = freightPerShipment / kitsPerShipment; - expect(perKit).toBe(7.00); - }); -}); - -describe("Kit Builder E2E Flow - Comparison", () => { - it("compares unit costs across kits", () => { - const kits = [ - { name: "Kit A", total: 400, quantity: 10 }, - { name: "Kit B", total: 300, quantity: 10 }, - { name: "Kit C", total: 500, quantity: 10 }, - ]; - const withUnitCost = kits.map(k => ({ ...k, unitCost: k.total / k.quantity })); - const cheapest = withUnitCost.reduce((a, b) => a.unitCost < b.unitCost ? a : b); - expect(cheapest.name).toBe("Kit B"); - }); - - it("identifies differences between kits", () => { - const kitA = { items: ["Caneta", "Caderno"] }; - const kitB = { items: ["Caneta", "Garrafa"] }; - const onlyInA = kitA.items.filter(i => !kitB.items.includes(i)); - const onlyInB = kitB.items.filter(i => !kitA.items.includes(i)); - expect(onlyInA).toEqual(["Caderno"]); - expect(onlyInB).toEqual(["Garrafa"]); - }); -}); - -describe("Kit Builder E2E Flow - Share Token", () => { - it("generates token structure", () => { - const token = { - kit_id: "kit-1", - seller_id: "seller-1", - client_name: "Cliente A", - client_email: "a@b.com", - status: "active", - }; - expect(token.status).toBe("active"); - expect(token.kit_id).toBeTruthy(); - }); - - it("validates token expiry", () => { - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - expect(expiresAt.getTime()).toBeGreaterThan(Date.now()); - }); - - it("marks token as viewed", () => { - const token = { status: "active", viewed_at: null as string | null }; - token.viewed_at = new Date().toISOString(); - expect(token.viewed_at).toBeTruthy(); - }); -}); - -describe("Kit Builder E2E Flow - Reset", () => { - it("resets all state correctly", () => { - let state = { - kitName: "Kit Teste", - kitType: "montado", - selectedBox: { id: "b1" }, - selectedItems: [{ id: "i1" }], - currentStep: "summary", - kitQuantity: 10, - }; - - // Reset - state = { - kitName: "", - kitType: "montado", - selectedBox: null as any, - selectedItems: [], - currentStep: "box", - kitQuantity: 1, - }; - - expect(state.kitName).toBe(""); - expect(state.selectedBox).toBeNull(); - expect(state.selectedItems).toHaveLength(0); - expect(state.currentStep).toBe("box"); - expect(state.kitQuantity).toBe(1); - }); -}); diff --git a/tests/e2e/mockup-regressions.spec.ts b/tests/e2e/mockup-regressions.spec.ts deleted file mode 100644 index 696d59b5c..000000000 --- a/tests/e2e/mockup-regressions.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Mockup Generator - Regressions', () => { - test.beforeEach(async ({ page }) => { - // Navigate and login if necessary (factory already handles this for some routes) - await page.goto('/mockup-generator'); - // Ensure the page didn't crash on load (the "Module Failure" check) - await expect(page.locator('h1')).toContainText('Gerador de Mockups'); - }); - - test('should not crash when showing generation errors (Tooltip check)', async ({ page }) => { - // We can simulate an error state via console or by triggering an action that fails - // But simplest is to check if Tooltip components are defined and don't throw on interaction - // Since it's a regression test for the import fix, we verify the UI is interactive - const generateBtn = page.getByTestId('mockup-generate-button'); - await expect(generateBtn).toBeVisible(); - }); - - test('should open TechniqueChangeDialog with correct props', async ({ page }) => { - // 1. Select a product to enable technique selection - // 2. Mock or select a client/logo if needed - // 3. Change technique twice to trigger the "Alterar técnica?" dialog - // This is a placeholder for a more complex interaction test - await expect(page.locator('button:has-text("Gerar Mockup")')).toBeVisible(); - }); - - test('should have strict-TS compliant types in MockupTechniqueHandlers', async ({ page }) => { - // This is checked by 'npm run typecheck', but we ensure the page is functional - await expect(page).not.toHaveTitle(/Error/); - }); -}); \ No newline at end of file diff --git a/tests/e2e/navigation-layout.test.tsx b/tests/e2e/navigation-layout.test.tsx deleted file mode 100644 index 2285f6404..000000000 --- a/tests/e2e/navigation-layout.test.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/** - * E2E Tests — Navigation & Layout Module - * Covers: Routing, Sidebar, Header, Breadcrumbs, Mobile Nav, Responsive, 404 - */ -import { describe, it, expect, vi } from 'vitest'; -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; - -// ============ Route Definitions ============ -const PUBLIC_ROUTES = ['/login', '/reset-password', '/approve/:token', '/proposta/:token', '/auth/callback']; -const PROTECTED_ROUTES = ['/', '/dashboard', '/produtos', '/produto/:id', '/novidades', '/favoritos', - '/carrinhos', '/comparar', '/colecoes', '/orcamentos', '/orcamentos/novo', '/orcamentos/kanban', - '/orcamentos/dashboard', '/orcamentos/templates', '/simulador', '/simulador-precos', '/estoque', - '/busca-preco', '/montar-kit', '/mockup-generator', '/magic-up', '/pedidos', '/perfil', '/bi', '/tendencias']; -const ADMIN_ROUTES = ['/admin/usuarios', '/admin/seguranca', '/admin/cadastros', '/admin/prompts-ia', - '/admin/telemetria', '/admin/permissoes', '/admin/roles', '/admin/role-permissoes', '/admin/rate-limit', - '/status', '/external-db-test']; -const REDIRECT_ROUTES = [ - { from: '/produto', to: '/produtos' }, - { from: '/filtros', to: '/produtos' }, - { from: '/configuracoes', to: '/admin/usuarios' }, - { from: '/admin', to: '/admin/usuarios' }, - { from: '/mockup', to: '/mockup-generator' }, - { from: '/seguranca', to: '/perfil' }, -]; - -describe('E2E Navigation — Route Registry', () => { - it('has public routes', () => expect(PUBLIC_ROUTES.length).toBeGreaterThan(0)); - it('has protected routes', () => expect(PROTECTED_ROUTES.length).toBeGreaterThanOrEqual(20)); - it('has admin routes', () => expect(ADMIN_ROUTES.length).toBeGreaterThanOrEqual(9)); - it('has redirect routes', () => expect(REDIRECT_ROUTES).toHaveLength(6)); - - PUBLIC_ROUTES.forEach(route => { - it(`public route "${route}" starts with /`, () => expect(route.startsWith('/')).toBe(true)); - }); - - PROTECTED_ROUTES.forEach(route => { - it(`protected route "${route}" is defined`, () => expect(route).toBeTruthy()); - }); - - ADMIN_ROUTES.forEach(route => { - it(`admin route "${route}" starts with /admin or /`, () => { - expect(route.startsWith('/admin') || route.startsWith('/status') || route.startsWith('/external')).toBe(true); - }); - }); - - REDIRECT_ROUTES.forEach(r => { - it(`redirect: ${r.from} → ${r.to}`, () => { - expect(r.from).not.toBe(r.to); - }); - }); -}); - -// ============ Sidebar Menu Items ============ -const SIDEBAR_SECTIONS = [ - { label: 'Catálogo', icon: 'Package', items: ['Produtos', 'Novidades', 'Favoritos', 'Comparar', 'Coleções'] }, - { label: 'Vendas', icon: 'ShoppingCart', items: ['Orçamentos', 'Carrinhos', 'Pedidos'] }, - { label: 'Ferramentas', icon: 'Wrench', items: ['Simulador', 'Mockup', 'Kit Builder', 'Busca de Preço'] }, - { label: 'Analytics', icon: 'BarChart', items: ['BI Dashboard', 'Tendências'] }, - { label: 'Administração', icon: 'Settings', items: ['Usuários', 'Segurança', 'Cadastros', 'Roles'] }, -]; - -describe('E2E Navigation — Sidebar Structure', () => { - it('has 5 sections', () => expect(SIDEBAR_SECTIONS).toHaveLength(5)); - - SIDEBAR_SECTIONS.forEach(section => { - it(`section "${section.label}" has items`, () => expect(section.items.length).toBeGreaterThan(0)); - it(`section "${section.label}" has icon`, () => expect(section.icon).toBeTruthy()); - }); - - it('total menu items >= 18', () => { - const total = SIDEBAR_SECTIONS.reduce((sum, s) => sum + s.items.length, 0); - expect(total).toBeGreaterThanOrEqual(18); - }); -}); - -// ============ Responsive Breakpoints ============ -describe('E2E Navigation — Responsive Breakpoints', () => { - const breakpoints = { sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536 }; - - Object.entries(breakpoints).forEach(([name, width]) => { - it(`breakpoint ${name} = ${width}px`, () => expect(width).toBeGreaterThan(0)); - }); - - it('mobile is < 768px', () => expect(breakpoints.md).toBe(768)); - it('tablet is >= 768px && < 1024px', () => { - expect(breakpoints.md).toBeLessThan(breakpoints.lg); - }); - it('desktop is >= 1024px', () => expect(breakpoints.lg).toBe(1024)); -}); - -// ============ 404 Page ============ -describe('E2E Navigation — 404 Page', () => { - it('NotFound is importable', async () => { - const mod = await import('@/pages/NotFound'); - expect(mod.default).toBeDefined(); - }); -}); - -// ============ Breadcrumb Path Parsing ============ -function parseBreadcrumbs(pathname: string): { label: string; href: string }[] { - const segments = pathname.split('/').filter(Boolean); - const crumbs = [{ label: 'Início', href: '/' }]; - const labelMap: Record = { - produtos: 'Produtos', produto: 'Produto', orcamentos: 'Orçamentos', - carrinhos: 'Carrinhos', admin: 'Admin', perfil: 'Perfil', - simulador: 'Simulador', pedidos: 'Pedidos', favoritos: 'Favoritos', - comparar: 'Comparar', novidades: 'Novidades', colecoes: 'Coleções', - bi: 'BI', tendencias: 'Tendências', estoque: 'Estoque', - }; - - segments.forEach((seg, i) => { - const href = '/' + segments.slice(0, i + 1).join('/'); - crumbs.push({ label: labelMap[seg] || seg, href }); - }); - return crumbs; -} - -describe('E2E Navigation — Breadcrumbs', () => { - it('root has only Início', () => expect(parseBreadcrumbs('/')).toHaveLength(1)); - it('/produtos has 2 crumbs', () => expect(parseBreadcrumbs('/produtos')).toHaveLength(2)); - it('/orcamentos/novo has 3 crumbs', () => expect(parseBreadcrumbs('/orcamentos/novo')).toHaveLength(3)); - it('/admin/usuarios has 3 crumbs', () => expect(parseBreadcrumbs('/admin/usuarios')).toHaveLength(3)); - it('first crumb is always Início', () => { - expect(parseBreadcrumbs('/qualquer/rota')[0].label).toBe('Início'); - }); - it('uses Portuguese labels', () => { - const crumbs = parseBreadcrumbs('/produtos'); - expect(crumbs[1].label).toBe('Produtos'); - }); -}); - -// ============ Command Bar (⌘K) ============ -describe('E2E Navigation — Command Bar', () => { - const commands = [ - { id: 'search', label: 'Buscar produto', shortcut: '⌘K', group: 'navigation' }, - { id: 'new-quote', label: 'Novo orçamento', shortcut: '⌘N', group: 'actions' }, - { id: 'goto-dashboard', label: 'Ir para Dashboard', shortcut: '⌘D', group: 'navigation' }, - { id: 'logout', label: 'Sair', shortcut: '⌘Q', group: 'system' }, - ]; - - it('has commands defined', () => expect(commands.length).toBeGreaterThan(0)); - it('each command has id', () => commands.forEach(c => expect(c.id).toBeTruthy())); - it('each command has label', () => commands.forEach(c => expect(c.label).toBeTruthy())); - it('each command has shortcut', () => commands.forEach(c => expect(c.shortcut).toBeTruthy())); - it('groups are valid', () => { - const groups = new Set(commands.map(c => c.group)); - expect(groups.size).toBeGreaterThanOrEqual(2); - }); -}); - -// ============ Scroll Behavior ============ -describe('E2E Navigation — Scroll', () => { - it('scroll to top threshold', () => { - const THRESHOLD = 150; - expect(THRESHOLD).toBeGreaterThan(0); - }); - - it('scroll progress ranges 0-100', () => { - const progress = Math.min(100, Math.max(0, 50)); - expect(progress).toBeGreaterThanOrEqual(0); - expect(progress).toBeLessThanOrEqual(100); - }); -}); diff --git a/tests/e2e/new-quote-advanced.test.tsx b/tests/e2e/new-quote-advanced.test.tsx deleted file mode 100644 index 60bd8174e..000000000 --- a/tests/e2e/new-quote-advanced.test.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import QuoteBuilderPage from '../../src/pages/quotes/QuoteBuilderPage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import fs from 'fs'; -import path from 'path'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -window.scrollTo = vi.fn(); - -// Mocks consolidados -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OrganizationContext', () => ({ - useOrganization: () => ({ organization: { id: 'org-123' }, isLoading: false }), - OrganizationProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { - getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }), - getSession: vi.fn().mockResolvedValue({ data: { session: { user: { id: 'u1' } } }, error: null }) - }, - from: vi.fn().mockReturnValue(chain), rpc: vi.fn().mockResolvedValue({ data: [] }), - functions: { invoke: vi.fn().mockResolvedValue({ data: {}, error: null }) } - }, - }; -}); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -const saveVisualEvidence = (name: string, container: HTMLElement) => { - const artifactDir = 'tests/e2e/artifacts/quotes/visual'; - if (!fs.existsSync(artifactDir)) fs.mkdirSync(artifactDir, { recursive: true }); - fs.writeFileSync(path.join(artifactDir, `${name}.html`), container.innerHTML); -}; - -describe('Módulo Novo Orçamento - Avançado Final', () => { - beforeEach(() => { - vi.clearAllMocks(); - sessionStorage.clear(); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Integridade: Carrega título da página e indica salvamento', async () => { - await renderPage(); - expect(await screen.findByText(/Novo Orçamento/i)).toBeInTheDocument(); - expect(screen.getByText(/Salvo automaticamente/i)).toBeInTheDocument(); - }); - - it('Estrutura: Valida seções principais por headings', async () => { - await renderPage(); - const headings = screen.getAllByRole('heading'); - const hasItens = headings.some(h => /Itens/i.test(h.textContent || '')); - expect(hasItens).toBeTruthy(); - }); - - it('Viewer: Gera artefatos visuais para auditoria CI', async () => { - const { container } = await renderPage(); - saveVisualEvidence('quote-builder-initial', container); - expect(fs.existsSync('tests/e2e/artifacts/quotes/visual/quote-builder-initial.html')).toBe(true); - }); -}); - diff --git a/tests/e2e/new-quote-cycle.test.tsx b/tests/e2e/new-quote-cycle.test.tsx deleted file mode 100644 index ecc6110c7..000000000 --- a/tests/e2e/new-quote-cycle.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import QuoteBuilderPage from '../../src/pages/quotes/QuoteBuilderPage'; -import { useComparisonStore } from '../../src/stores/useComparisonStore'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import fs from 'fs'; -import path from 'path'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -window.scrollTo = vi.fn(); - -// Consolidate mocks for stability -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OrganizationContext', () => ({ - useOrganization: () => ({ organization: { id: 'org-123' }, isLoading: false }), - OrganizationProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { - getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }), - getSession: vi.fn().mockResolvedValue({ data: { session: { user: { id: 'u1' } } }, error: null }) - }, - from: vi.fn().mockReturnValue(chain), rpc: vi.fn().mockResolvedValue({ data: [] }), - functions: { invoke: vi.fn().mockResolvedValue({ data: {}, error: null }) } - }, - }; -}); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -describe('Módulo Novo Orçamento - Ciclo Final', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Integridade: Carrega título e indicador de salvamento', async () => { - await renderPage(); - expect(await screen.findByText(/Novo Orçamento/i)).toBeInTheDocument(); - expect(screen.getByText(/Salvo automaticamente/i)).toBeInTheDocument(); - }); - - it('Estrutura: Valida seções de orçamento via heading roles', async () => { - await renderPage(); - const headings = screen.getAllByRole('heading'); - const hasItens = headings.some(h => /Itens/i.test(h.textContent || '')); - expect(hasItens).toBeTruthy(); - }); -}); - diff --git a/tests/e2e/new-quote-exhaustive.test.ts b/tests/e2e/new-quote-exhaustive.test.ts deleted file mode 100644 index 8ee68eb3b..000000000 --- a/tests/e2e/new-quote-exhaustive.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { test, expect } from "@playwright/test"; -import fs from 'fs'; -import path from 'path'; - -/** - * Módulo: Novo Orçamento (E2E Exaustivo) - * Objetivo: Avaliar cada módulo, função, botão e camada do sistema de orçamentos. - */ - -test.describe("Módulo Novo Orçamento - Testes Exaustivos", () => { - const evidenceDir = 'tests/e2e/evidence/quotes'; - - test.beforeAll(async () => { - if (!fs.existsSync(evidenceDir)) fs.mkdirSync(evidenceDir, { recursive: true }); - }); - - test.beforeEach(async ({ page }) => { - // Configura captura de erros para auditoria - page.on('console', msg => { - if (msg.type() === 'error') { - const logLine = `[ERROR] ${msg.text()}\n`; - fs.appendFileSync(path.join(evidenceDir, 'quotes-error-audit.log'), logLine); - } - }); - - await page.goto("/orcamentos/novo"); - }); - - test("Cenário 1: Fluxo de Identificação - Empresa e Contato", async ({ page }) => { - // Valida título da página - await expect(page.locator("h1")).toContainText("Novo Orçamento"); - await page.screenshot({ path: `${evidenceDir}/01-initial-state.png` }); - - // Tenta salvar sem preencher nada (validação de obrigatoriedade) - await page.click("text=Gerar Orçamento"); - await expect(page.locator("text=Selecione uma empresa")).toBeVisible(); - await expect(page.locator("text=Selecione um contato")).toBeVisible(); - - // Seleciona Empresa e Contato (seletores de Select/Popover do shadcn) - // Assumindo que o seletor abre uma busca ou lista - const companyTrigger = page.locator('button:has-text("Empresa")').or(page.locator('button:has-text("Selecione a empresa")')).first(); - await companyTrigger.click(); - // Simula seleção do primeiro item se disponível ou digitação - await page.keyboard.type("Cliente Teste"); - await page.keyboard.press("Enter"); - - await page.screenshot({ path: `${evidenceDir}/02-client-selected.png` }); - }); - - test("Cenário 2: Adição e Customização de Produtos", async ({ page }) => { - // Abre busca de produtos - await page.click('button:has-text("Produto")'); - await expect(page.locator("text=Buscar Produtos")).toBeVisible(); - - // Digita busca e seleciona primeiro produto - await page.fill('input[placeholder*="Buscar"]', "Caneta"); - await page.waitForTimeout(500); // Aguarda debounce - const addBtn = page.locator('button:has-text("Adicionar"), button[aria-label*="Adicionar"]').first(); - await addBtn.click(); - - // Valida que produto foi adicionado à lista lateral - await expect(page.locator("text=1 item(ns) adicionado(s)")).toBeVisible(); - - // Customização: Altera quantidade e valida recálculo - const qtyInput = page.locator('input[type="number"]').first(); - await qtyInput.fill("100"); - await page.keyboard.press("Tab"); - - // Valida que o resumo atualizou - await expect(page.locator("text=R$")).toBeVisible(); - await page.screenshot({ path: `${evidenceDir}/03-product-customized.png` }); - }); - - test("Cenário 3: Condições Comerciais e Frete", async ({ page }) => { - // Seleciona Prazo de Pagamento - const paymentTrigger = page.locator('button:has-text("Prazo | Pagamento")').or(page.locator('label:has-text("Pagamento") + div button')).first(); - await paymentTrigger.click(); - await page.click("text=21 dias"); - - // Seleciona Frete FOB e preenche valor - const shippingTrigger = page.locator('button:has-text("Frete")').first(); - await shippingTrigger.click(); - await page.click("text=FOB"); - - const shippingValue = page.locator('input[placeholder="0,00"]'); - await shippingValue.fill("50.00"); - - await expect(page.locator("text=R$ 50,00")).toBeVisible(); - await page.screenshot({ path: `${evidenceDir}/04-commercial-conditions.png` }); - }); - - test("Cenário 4: Uso de Templates e AutoSave", async ({ page }) => { - // Verifica AutoSave - const autoSaveIndicator = page.locator("text=Salvo").or(page.locator("text=Sincronizando")); - await expect(autoSaveIndicator).toBeVisible(); - - // Testa aplicação de Template - const templateBtn = page.locator("button:has-text('Usar Template')"); - if (await templateBtn.isVisible()) { - await templateBtn.click(); - const firstTemplate = page.locator('role=menuitem').first(); - if (await firstTemplate.isVisible()) { - await firstTemplate.click(); - await expect(page.locator("text=aplicado")).toBeVisible(); - } - } - }); - - test("Cenário 5: Finalização e Validação de Erros de Negócio", async ({ page }) => { - // Tenta finalizar orçamento incompleto - await page.click("text=Gerar Orçamento"); - - // Se faltarem dados obrigatórios (ex: frete não preenchido em modo FOB), deve alertar - const errorAlert = page.locator("text=obrigatório").or(page.locator(".text-destructive")); - if (await errorAlert.count() > 0) { - await page.screenshot({ path: `${evidenceDir}/05-validation-errors.png` }); - } - }); - - test("Cenário 6: Acessibilidade e Navegação no Builder", async ({ page }) => { - // Testa ordem de foco no formulário - await page.keyboard.press("Tab"); - const focused = await page.evaluate(() => document.activeElement?.tagName); - expect(focused).toBeDefined(); - - // Verifica rótulos ARIA nos campos críticos - const companyLabel = await page.getAttribute('button[aria-haspopup="dialog"]', 'aria-label'); - // expect(companyLabel).toBeDefined(); // Opcional dependendo da implementação exata - }); - -}); diff --git a/tests/e2e/new-quote-exhaustive.test.tsx b/tests/e2e/new-quote-exhaustive.test.tsx deleted file mode 100644 index abf7317ba..000000000 --- a/tests/e2e/new-quote-exhaustive.test.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, waitFor, fireEvent, within } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import QuoteBuilderPage from '../../src/pages/quotes/QuoteBuilderPage'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -window.scrollTo = vi.fn(); - -// Mocks consolidados para estabilidade -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1', email: 'test@example.com' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false, startTour: vi.fn(), completeTour: vi.fn() }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OrganizationContext', () => ({ - useOrganization: () => ({ organization: { id: 'org-123' }, isLoading: false }), - OrganizationProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), - eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), - order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), - maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { - getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }), - getSession: vi.fn().mockResolvedValue({ data: { session: { user: { id: 'u1' } } }, error: null }) - }, - from: vi.fn().mockReturnValue(chain), - rpc: vi.fn().mockResolvedValue({ data: [] }), - functions: { invoke: vi.fn().mockResolvedValue({ data: {}, error: null }) } - }, - }; -}); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -// Mock do hook useQuotes para evitar chamadas externas de técnicas -vi.mock('../../src/hooks/quotes/useQuotes', () => ({ - useQuotes: () => ({ - createQuote: vi.fn().mockResolvedValue({ id: 'q123' }), - updateQuote: vi.fn().mockResolvedValue({ id: 'q123' }), - techniques: [{ id: 't1', name: 'Laser', base_cost: 5 }], - isLoadingTechniques: false, - }), -})); - -describe('Módulo Novo Orçamento - Auditoria Detalhada de Funcionalidades', () => { - beforeEach(() => { - vi.clearAllMocks(); - localStorage.clear(); - sessionStorage.clear(); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => expect(screen.getByText(/Novo Orçamento/i)).toBeInTheDocument()); - return res; - }; - - it('Validação 1: Componentes Críticos e Stepper', async () => { - await renderPage(); - - // Verifica o Stepper (Wizard) - const stepper = screen.getByTestId('quote-wizard'); - expect(stepper).toBeInTheDocument(); - - // Verifica passos do Stepper - expect(within(stepper).getByText(/Cliente/i)).toBeInTheDocument(); - expect(within(stepper).getByText(/Itens/i)).toBeInTheDocument(); - expect(within(stepper).getByText(/Condições/i)).toBeInTheDocument(); - expect(within(stepper).getByText(/Revisão/i)).toBeInTheDocument(); - }); - - it('Validação 2: Cálculos e Desconto (Refatoração SOLID)', async () => { - await renderPage(); - - // Verifica se a área de resumo (Total) está presente - expect(screen.getByText(/Total/i)).toBeInTheDocument(); - expect(screen.getByText(/R\$ 0,00/i)).toBeInTheDocument(); - - // Tenta encontrar o seletor de desconto - const discountInput = screen.queryByPlaceholderText(/Desconto/i); - // Nota: Pode não aparecer se não houver itens, depende da UI - }); - - it('Validação 3: Resiliência e AutoSave (Local Storage)', async () => { - await renderPage(); - - // Simula entrada de dados para trigger do AutoSave - // Como os inputs são complexos (Selects), validamos a existência do hook injetado via logs se necessário - // ou verificando se o localStorage é manipulado - - // Testamos a limpeza do AutoSave após "salvar" (simulado) - const saveDraftBtn = screen.getByText(/Salvar Rascunho/i); - expect(saveDraftBtn).toBeInTheDocument(); - }); - - it('Acessibilidade: Estrutura Semântica e Focus Management', async () => { - await renderPage(); - - // Botões devem ter labels acessíveis - const productBtn = screen.getByRole('button', { name: /produto/i }); - expect(productBtn).toBeInTheDocument(); - - // Verifica H1 único - const h1s = screen.getAllByRole('heading', { level: 1 }); - expect(h1s).toHaveLength(1); - expect(h1s[0]).toHaveTextContent(/Novo Orçamento/i); - }); - - it('Regressão: Verificação de "Estoque" vs "Dashboard"', async () => { - await renderPage(); - - // Garante que termos antigos foram removidos - const dashboardText = screen.queryByText(/Dashboard de Estoque/i); - expect(dashboardText).toBeNull(); - }); -}); diff --git a/tests/e2e/new-quote-full-audit.test.tsx b/tests/e2e/new-quote-full-audit.test.tsx deleted file mode 100644 index 4390f34d8..000000000 --- a/tests/e2e/new-quote-full-audit.test.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import QuoteBuilderPage from '../../src/pages/quotes/QuoteBuilderPage'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import fs from 'fs'; -import path from 'path'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -window.scrollTo = vi.fn(); - -// Mocks consolidados para estabilidade -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OrganizationContext', () => ({ - useOrganization: () => ({ organization: { id: 'org-123' }, isLoading: false }), - OrganizationProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { - getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }), - getSession: vi.fn().mockResolvedValue({ data: { session: { user: { id: 'u1' } } }, error: null }) - }, - from: vi.fn().mockReturnValue(chain), rpc: vi.fn().mockResolvedValue({ data: [] }), - functions: { invoke: vi.fn().mockResolvedValue({ data: {}, error: null }) } - }, - }; -}); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -describe('Novo Orçamento - Auditoria de Regressão e Resiliência Final', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Integridade: Título e indicador de AutoSave carregam no estado inicial', async () => { - await renderPage(); - expect(await screen.findByText(/Novo Orçamento/i)).toBeInTheDocument(); - expect(screen.getByText(/Salvo automaticamente/i)).toBeInTheDocument(); - }); - - it('Resiliência: Valida estrutura de navegação e botões de ação', async () => { - await renderPage(); - expect(screen.getByLabelText(/Voltar/i)).toBeInTheDocument(); - const comboboxes = screen.getAllByRole('combobox'); - expect(comboboxes.length).toBeGreaterThan(0); - }); - - it('CI Visual Report: Gera artefatos técnicos para auditoria de seções', async () => { - const { container } = await renderPage(); - const artifactPath = 'tests/e2e/artifacts/quotes/audit-report'; - if (!fs.existsSync(artifactPath)) fs.mkdirSync(artifactPath, { recursive: true }); - - fs.writeFileSync(path.join(artifactPath, 'builder-full.html'), container.innerHTML); - const headings = screen.getAllByRole('heading'); - expect(headings.length).toBeGreaterThan(0); - }); -}); - diff --git a/tests/e2e/new-quote-resilience.test.tsx b/tests/e2e/new-quote-resilience.test.tsx deleted file mode 100644 index 29e033236..000000000 --- a/tests/e2e/new-quote-resilience.test.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import QuoteBuilderPage from '../../src/pages/quotes/QuoteBuilderPage'; -import { TooltipProvider } from '../../src/components/ui/tooltip'; -import { HelmetProvider } from 'react-helmet-async'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import fs from 'fs'; -import path from 'path'; - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}); - -window.scrollTo = vi.fn(); - -// Mocks consolidados para infraestrutura e contextos -vi.mock('../../src/components/a11y/AriaLive', () => ({ - useAriaLive: () => ({ announce: vi.fn(), announceStatus: vi.fn() }), - AriaLiveProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/AuthContext', () => ({ - useAuth: () => ({ user: { id: 'u1' }, isAuthenticated: true, role: 'agente' }), - AuthProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OnboardingContext', () => ({ - useOnboarding: () => ({ isTourOpen: false }), - useOnboardingContext: () => ({ isTourOpen: false }), - useOptionalOnboardingContext: () => null, - OnboardingProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/SellerCartContext', () => ({ - useSellerCart: () => ({ items: [] }), - SellerCartProvider: ({ children }: any) =>
{children}
, -})); - -vi.mock('../../src/contexts/OrganizationContext', () => ({ - useOrganization: () => ({ organization: { id: 'org-123' }, isLoading: false }), - OrganizationProvider: ({ children }: any) =>
{children}
, -})); - -// Mock do Supabase simulando falhas de AutoSave e recuperação -vi.mock('../../src/integrations/supabase/client', () => { - const chain = { - select: vi.fn().mockReturnThis(), eq: vi.fn().mockReturnThis(), - is: vi.fn().mockReturnThis(), order: vi.fn().mockReturnThis(), - limit: vi.fn().mockReturnThis(), maybeSingle: vi.fn().mockResolvedValue({ data: null }), - then: vi.fn().mockImplementation((cb) => Promise.resolve({ data: [] }).then(cb)), - }; - return { - supabase: { - auth: { - getUser: vi.fn().mockResolvedValue({ data: { user: { id: 'u1' } } }), - getSession: vi.fn().mockResolvedValue({ data: { session: { user: { id: 'u1' } } }, error: null }) - }, - from: vi.fn().mockReturnValue(chain), rpc: vi.fn().mockResolvedValue({ data: [] }), - functions: { - invoke: vi.fn().mockImplementation((fn) => { - if (fn === "comparison-ai-advisor") return Promise.reject(new Error("Network Error")); - return Promise.resolve({ data: {}, error: null }); - }) - } - }, - }; -}); - -vi.mock('../../src/components/layout/MainLayout', () => ({ - MainLayout: ({ children }: any) =>
{children}
, -})); - -describe('Módulo Novo Orçamento - Resiliência e Acessibilidade Estável', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - const renderPage = async () => { - const res = render( - - - - - - - - - - ); - await waitFor(() => {}); - return res; - }; - - it('Integridade: Título e indicador de salvamento carregam corretamente', async () => { - await renderPage(); - expect(await screen.findByText(/Novo Orçamento/i)).toBeInTheDocument(); - expect(screen.getByText(/Salvo automaticamente/i)).toBeInTheDocument(); - }); - - it('Resiliência: Valida que campos de entrada estão presentes', async () => { - await renderPage(); - const inputs = screen.getAllByRole('combobox'); - expect(inputs.length).toBeGreaterThan(0); - }); - - it('CI Visual: Gera snapshot técnico e valida headings de seção', async () => { - const { container } = await renderPage(); - const artifactDir = 'tests/e2e/artifacts/quotes/ci-final-resilient'; - if (!fs.existsSync(artifactDir)) fs.mkdirSync(artifactDir, { recursive: true }); - - fs.writeFileSync(path.join(artifactDir, 'builder-snapshot.html'), container.innerHTML); - const headings = screen.getAllByRole('heading'); - const hasAnySection = headings.some(h => /Itens|Condições|Identificação/i.test(h.textContent || '')); - expect(hasAnySection).toBeTruthy(); - }); -}); - - diff --git a/tests/e2e/performance.test.ts b/tests/e2e/performance.test.ts deleted file mode 100644 index b03976091..000000000 --- a/tests/e2e/performance.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * E2E Tests — Performance & Code Quality - * Covers: Bundle analysis, lazy loading, pagination limits, debounce, caching - */ -import { describe, it, expect } from 'vitest'; - -// ============ Lazy Loading ============ -describe('E2E Performance — Lazy Loading', () => { - const lazyComponents = [ - 'Header', 'SidebarReorganized', 'PageTransition', 'OnboardingTour', - 'ExpertChatButton', 'EnhancedSpotlight', 'SmartMobileNav', - 'QuickQuoteFAB', 'FloatingCompareBar', 'GlobalCommandBar', - 'ScrollToTopButton', 'ScrollProgressIndicator', - ]; - - it('MainLayout lazy loads 12+ components', () => expect(lazyComponents.length).toBeGreaterThanOrEqual(12)); - lazyComponents.forEach(c => { - it(`"${c}" is lazy loaded`, () => expect(c).toBeTruthy()); - }); -}); - -// ============ Pagination Limits ============ -describe('E2E Performance — Query Limits', () => { - const SUPABASE_DEFAULT_LIMIT = 1000; - const APP_PAGE_SIZE = 20; - - it('app page size < supabase limit', () => expect(APP_PAGE_SIZE).toBeLessThan(SUPABASE_DEFAULT_LIMIT)); - it('page size is reasonable', () => expect(APP_PAGE_SIZE).toBeGreaterThanOrEqual(10)); - it('page size is <= 50', () => expect(APP_PAGE_SIZE).toBeLessThanOrEqual(50)); -}); - -// ============ Debounce Timing ============ -describe('E2E Performance — Debounce', () => { - const SEARCH_DEBOUNCE = 300; - const FILTER_DEBOUNCE = 200; - const AUTOSAVE_DEBOUNCE = 2000; - - it('search debounce 300ms', () => expect(SEARCH_DEBOUNCE).toBe(300)); - it('filter debounce 200ms', () => expect(FILTER_DEBOUNCE).toBe(200)); - it('autosave debounce 2s', () => expect(AUTOSAVE_DEBOUNCE).toBe(2000)); - it('search < autosave', () => expect(SEARCH_DEBOUNCE).toBeLessThan(AUTOSAVE_DEBOUNCE)); -}); - -// ============ Image Optimization ============ -describe('E2E Performance — Image Optimization', () => { - const supportedFormats = ['image/png', 'image/jpeg', 'image/webp']; - const MAX_IMAGE_SIZE_MB = 5; - - it('supports 3 formats', () => expect(supportedFormats).toHaveLength(3)); - it('max size is 5MB', () => expect(MAX_IMAGE_SIZE_MB).toBe(5)); - - it('validates file size', () => { - const sizeInBytes = 3 * 1024 * 1024; - expect(sizeInBytes <= MAX_IMAGE_SIZE_MB * 1024 * 1024).toBe(true); - }); -}); - -// ============ Cache Strategy ============ -describe('E2E Performance — React Query Cache', () => { - const cacheConfig = { - staleTime: 5 * 60 * 1000, // 5 minutes - gcTime: 10 * 60 * 1000, // 10 minutes - retryCount: 3, - retryDelay: 1000, - }; - - it('stale time is 5 min', () => expect(cacheConfig.staleTime).toBe(300000)); - it('gc time > stale time', () => expect(cacheConfig.gcTime).toBeGreaterThan(cacheConfig.staleTime)); - it('retry count is 3', () => expect(cacheConfig.retryCount).toBe(3)); - it('retry delay is 1s', () => expect(cacheConfig.retryDelay).toBe(1000)); -}); - -// ============ Bundle Size Targets ============ -describe('E2E Performance — Bundle Targets', () => { - const targets = { - mainChunkKB: 250, - vendorChunkKB: 500, - totalKB: 1500, - ttiBudgetMs: 3000, - lcpBudgetMs: 2500, - }; - - it('main chunk < 250KB', () => expect(targets.mainChunkKB).toBeLessThanOrEqual(250)); - it('vendor chunk < 500KB', () => expect(targets.vendorChunkKB).toBeLessThanOrEqual(500)); - it('total < 1.5MB', () => expect(targets.totalKB).toBeLessThanOrEqual(1500)); - it('TTI budget 3s', () => expect(targets.ttiBudgetMs).toBe(3000)); - it('LCP budget 2.5s', () => expect(targets.lcpBudgetMs).toBe(2500)); -}); - -// ============ Error Boundaries ============ -describe('E2E Performance — Error Handling', () => { - const errorStrategies = ['retry', 'fallback', 'toast', 'log', 'report']; - - it('has 5 error strategies', () => expect(errorStrategies).toHaveLength(5)); - it('includes retry', () => expect(errorStrategies).toContain('retry')); - it('includes fallback', () => expect(errorStrategies).toContain('fallback')); - it('includes reporting', () => expect(errorStrategies).toContain('report')); -}); diff --git a/tests/e2e/phase1-foundation-types.test.ts b/tests/e2e/phase1-foundation-types.test.ts deleted file mode 100644 index 8508d200d..000000000 --- a/tests/e2e/phase1-foundation-types.test.ts +++ /dev/null @@ -1,284 +0,0 @@ -/** - * Phase 1: Foundation — Types & Data Model Validation - * Validates the product variation type system, domain types, - * and infrastructure alignment for Color, Size, and Gender axes. - */ -import { describe, it, expect } from 'vitest'; - -// ============ 1.1 — ProductColor Interface ============ -describe('Phase 1.1 — ProductColor Type Contract', () => { - interface ProductColor { - code: string; - name: string; - hex?: string; - stock?: number; - } - - const validColor: ProductColor = { code: 'BLK', name: 'Preto', hex: '#000000', stock: 500 }; - - it('accepts valid color with all fields', () => { - expect(validColor.code).toBe('BLK'); - expect(validColor.name).toBe('Preto'); - expect(validColor.hex).toBe('#000000'); - expect(validColor.stock).toBe(500); - }); - - it('accepts color without optional fields', () => { - const minimal: ProductColor = { code: 'WHT', name: 'Branco' }; - expect(minimal.hex).toBeUndefined(); - expect(minimal.stock).toBeUndefined(); - }); - - it('handles zero stock', () => { - const noStock: ProductColor = { code: 'RED', name: 'Vermelho', stock: 0 }; - expect(noStock.stock).toBe(0); - }); - - it('handles large stock numbers', () => { - const large: ProductColor = { code: 'BLU', name: 'Azul', stock: 999_999 }; - expect(large.stock).toBe(999_999); - }); - - it('handles hex with different formats', () => { - expect('#FF0000'.match(/^#[0-9A-Fa-f]{6}$/)).toBeTruthy(); - expect('#fff'.match(/^#[0-9A-Fa-f]{3}$/)).toBeTruthy(); - expect('invalid').not.toMatch(/^#[0-9A-Fa-f]{6}$/); - }); -}); - -// ============ 1.2 — ProductVariant Interface (with size_code) ============ -describe('Phase 1.2 — ProductVariant with size_code', () => { - interface ProductVariant { - code: string; - name: string; - hex?: string; - stock?: number; - size_code?: string | null; - sale_price?: number | null; - } - - it('variant without size_code (legacy compat)', () => { - const legacy: ProductVariant = { code: 'V1', name: 'Preto' }; - expect(legacy.size_code).toBeUndefined(); - }); - - it('variant with null size_code', () => { - const noSize: ProductVariant = { code: 'V2', name: 'Azul', size_code: null }; - expect(noSize.size_code).toBeNull(); - }); - - it('variant with valid size_code', () => { - const withSize: ProductVariant = { code: 'V3', name: 'Preto', size_code: 'M', stock: 150 }; - expect(withSize.size_code).toBe('M'); - }); - - it('variant with numeric size_code', () => { - const numericSize: ProductVariant = { code: 'V4', name: 'Marrom', size_code: '42' }; - expect(numericSize.size_code).toBe('42'); - }); - - it('variant with sale_price override', () => { - const priced: ProductVariant = { - code: 'V5', name: 'Preto', size_code: 'GG', sale_price: 59.90, - }; - expect(priced.sale_price).toBe(59.90); - }); - - it('variant with null sale_price (uses product base)', () => { - const base: ProductVariant = { code: 'V6', name: 'Branco', sale_price: null }; - expect(base.sale_price).toBeNull(); - }); - - it('variant grouping by color_name for grid', () => { - const variants: ProductVariant[] = [ - { code: 'V1', name: 'Preto', size_code: 'P' }, - { code: 'V2', name: 'Preto', size_code: 'M' }, - { code: 'V3', name: 'Preto', size_code: 'G' }, - { code: 'V4', name: 'Azul', size_code: 'P' }, - { code: 'V5', name: 'Azul', size_code: 'M' }, - ]; - const grouped = new Map(); - variants.forEach(v => { - if (!grouped.has(v.name)) grouped.set(v.name, []); - grouped.get(v.name)!.push(v); - }); - expect(grouped.get('Preto')).toHaveLength(3); - expect(grouped.get('Azul')).toHaveLength(2); - expect(grouped.size).toBe(2); - }); -}); - -// ============ 1.3 — Gender Field Standardization ============ -describe('Phase 1.3 — Gender Field', () => { - const VALID_GENDERS = ['unissex', 'masculino', 'feminino', 'infantil']; - - it('accepts all valid gender values', () => { - VALID_GENDERS.forEach(g => expect(VALID_GENDERS).toContain(g)); - }); - - it('normalizes to lowercase', () => { - const inputs = ['MASCULINO', 'Feminino', 'UNISSEX', 'Infantil']; - const normalized = inputs.map(g => g.toLowerCase()); - normalized.forEach(g => expect(VALID_GENDERS).toContain(g)); - }); - - it('rejects invalid gender', () => { - expect(VALID_GENDERS).not.toContain('outro'); - expect(VALID_GENDERS).not.toContain(''); - expect(VALID_GENDERS).not.toContain(null); - }); - - it('handles null/undefined gender gracefully', () => { - const product = { name: 'Caneta', gender: null as string | null }; - expect(product.gender).toBeNull(); - const product2 = { name: 'Caderno' }; - expect((product2 as any).gender).toBeUndefined(); - }); - - it('trims whitespace in gender', () => { - const raw = ' masculino '; - expect(raw.toLowerCase().trim()).toBe('masculino'); - }); -}); - -// ============ 1.4 — SimulationProduct Domain Type ============ -describe('Phase 1.4 — SimulationProduct Domain', () => { - interface SimulationProduct { - id: string; - name: string; - sku: string; - price: number; - image_url?: string | null; - images?: string[]; - categoryName?: string | null; - brand?: string | null; - colors?: Array<{ code: string; name: string; hex?: string; stock?: number }>; - } - - it('minimal product for simulation', () => { - const p: SimulationProduct = { id: '1', name: 'Caneta BIC', sku: 'CAN-001', price: 5.50 }; - expect(p.price).toBe(5.50); - expect(p.colors).toBeUndefined(); - }); - - it('product with colors array', () => { - const p: SimulationProduct = { - id: '2', name: 'Camiseta', sku: 'CAM-001', price: 35, - colors: [ - { code: 'BLK', name: 'Preto', hex: '#000', stock: 200 }, - { code: 'WHT', name: 'Branco', hex: '#FFF', stock: 0 }, - ], - }; - expect(p.colors).toHaveLength(2); - expect(p.colors![1].stock).toBe(0); - }); - - it('product with empty colors array', () => { - const p: SimulationProduct = { id: '3', name: 'Squeeze', sku: 'SQZ-001', price: 25, colors: [] }; - expect(p.colors).toHaveLength(0); - }); -}); - -// ============ 1.5 — KitItem with selectedSize ============ -describe('Phase 1.5 — KitItem selectedSize field', () => { - interface KitItem { - id: string; - name: string; - price: number; - selectedColor?: { name: string; hex?: string }; - selectedSize?: string; - quantity: number; - } - - it('kit item without size (non-apparel)', () => { - const item: KitItem = { id: '1', name: 'Caneta', price: 5, quantity: 1 }; - expect(item.selectedSize).toBeUndefined(); - }); - - it('kit item with color and size', () => { - const item: KitItem = { - id: '2', name: 'Camiseta', price: 35, quantity: 1, - selectedColor: { name: 'Preto', hex: '#000' }, - selectedSize: 'G', - }; - expect(item.selectedSize).toBe('G'); - expect(item.selectedColor!.name).toBe('Preto'); - }); - - it('kit item size can be changed', () => { - const item: KitItem = { id: '3', name: 'Polo', price: 42, quantity: 1, selectedSize: 'M' }; - const updated = { ...item, selectedSize: 'G' }; - expect(updated.selectedSize).toBe('G'); - expect(item.selectedSize).toBe('M'); // original unchanged - }); -}); - -// ============ 1.6 — VariantGridItem Type ============ -describe('Phase 1.6 — VariantGridItem contract', () => { - interface VariantGridItem { - id: string; - color_name: string; - color_hex: string; - size_code?: string | null; - stock: number; - sku?: string; - image?: string | null; - price?: number | null; - } - - it('grid item with all fields', () => { - const item: VariantGridItem = { - id: 'v1', color_name: 'Preto', color_hex: '#000000', - size_code: 'M', stock: 150, sku: 'CAM-001-M-BLK', price: 35.90, - }; - expect(item.size_code).toBe('M'); - expect(item.stock).toBe(150); - }); - - it('grid item without size (color-only product)', () => { - const item: VariantGridItem = { - id: 'v2', color_name: 'Azul', color_hex: '#0000FF', stock: 500, - }; - expect(item.size_code).toBeUndefined(); - }); - - it('grid item with zero stock', () => { - const item: VariantGridItem = { - id: 'v3', color_name: 'Verde', color_hex: '#00FF00', stock: 0, - }; - expect(item.stock).toBe(0); - }); -}); - -// ============ 1.7 — BulkAction Type ============ -describe('Phase 1.7 — BulkAction type', () => { - interface BulkAction { - type: 'toggle_active' | 'update_stock'; - variantIds: string[]; - value?: boolean | number; - } - - it('toggle_active action', () => { - const action: BulkAction = { type: 'toggle_active', variantIds: ['v1', 'v2'], value: false }; - expect(action.type).toBe('toggle_active'); - expect(action.variantIds).toHaveLength(2); - expect(action.value).toBe(false); - }); - - it('update_stock action', () => { - const action: BulkAction = { type: 'update_stock', variantIds: ['v1', 'v2', 'v3'], value: 100 }; - expect(action.type).toBe('update_stock'); - expect(action.value).toBe(100); - }); - - it('action with empty variantIds', () => { - const action: BulkAction = { type: 'toggle_active', variantIds: [] }; - expect(action.variantIds).toHaveLength(0); - }); - - it('action with single variant', () => { - const action: BulkAction = { type: 'update_stock', variantIds: ['v1'], value: 0 }; - expect(action.variantIds).toHaveLength(1); - expect(action.value).toBe(0); - }); -}); diff --git a/tests/e2e/phase2-filters-catalog.test.ts b/tests/e2e/phase2-filters-catalog.test.ts deleted file mode 100644 index 13cdfc686..000000000 --- a/tests/e2e/phase2-filters-catalog.test.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Phase 2: Filters & Catalog — Gender Filter, Size Filter, Gender Badge - * Validates filter logic, size ordering, gender normalization, and badge rendering rules. - */ -import { describe, it, expect } from 'vitest'; - -// ============ 2.1 — Gender Filter Logic ============ -describe('Phase 2.1 — Gender Filter Integration', () => { - const GENDERS = ['unissex', 'masculino', 'feminino', 'infantil'] as const; - - const products = [ - { id: '1', name: 'Camiseta Masc', gender: 'masculino' }, - { id: '2', name: 'Camiseta Fem', gender: 'feminino' }, - { id: '3', name: 'Camiseta Unissex', gender: 'unissex' }, - { id: '4', name: 'Caneta', gender: null }, - { id: '5', name: 'Blusa Infantil', gender: 'infantil' }, - { id: '6', name: 'Mochila', gender: undefined }, - ]; - - function filterByGender(items: typeof products, selected: string[]): typeof products { - if (selected.length === 0) return items; - return items.filter(p => p.gender && selected.includes(p.gender)); - } - - it('no filter returns all products', () => { - expect(filterByGender(products, [])).toHaveLength(6); - }); - - it('filter masculino', () => { - const result = filterByGender(products, ['masculino']); - expect(result).toHaveLength(1); - expect(result[0].name).toBe('Camiseta Masc'); - }); - - it('filter feminino', () => { - const result = filterByGender(products, ['feminino']); - expect(result).toHaveLength(1); - expect(result[0].name).toBe('Camiseta Fem'); - }); - - it('filter multiple genders', () => { - const result = filterByGender(products, ['masculino', 'feminino']); - expect(result).toHaveLength(2); - }); - - it('filter infantil', () => { - const result = filterByGender(products, ['infantil']); - expect(result).toHaveLength(1); - expect(result[0].name).toBe('Blusa Infantil'); - }); - - it('filter unissex', () => { - const result = filterByGender(products, ['unissex']); - expect(result).toHaveLength(1); - }); - - it('filter all genders returns all with gender', () => { - const result = filterByGender(products, [...GENDERS]); - expect(result).toHaveLength(4); // excludes null and undefined - }); - - it('products without gender excluded from gender filter', () => { - const result = filterByGender(products, ['masculino']); - expect(result.some(p => p.id === '4')).toBe(false); - expect(result.some(p => p.id === '6')).toBe(false); - }); - - it('handles case-insensitive gender normalization', () => { - const rawProducts = [ - { id: '1', name: 'A', gender: 'MASCULINO' }, - { id: '2', name: 'B', gender: 'Feminino' }, - ]; - const normalized = rawProducts.map(p => ({ - ...p, gender: p.gender?.toLowerCase(), - })); - const result = normalized.filter(p => p.gender === 'masculino'); - expect(result).toHaveLength(1); - }); -}); - -// ============ 2.2 — Size Filter Logic ============ -describe('Phase 2.2 — Size Filter', () => { - const SIZE_ORDER = [ - 'PP', 'P', 'M', 'G', 'GG', 'XG', 'XXG', 'EG', 'EGG', - 'XS', 'S', 'L', 'XL', 'XXL', '2XL', '3XL', - '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', - '100ml', '200ml', '300ml', '350ml', '400ml', '500ml', '600ml', '750ml', '1L', - ]; - - function getSizeOrder(code: string): number { - const upper = code.toUpperCase().trim(); - const idx = SIZE_ORDER.indexOf(upper); - if (idx >= 0) return idx; - const num = parseFloat(upper); - if (!isNaN(num)) return 1000 + num; - return 2000; - } - - it('standard clothing sizes order correctly', () => { - const sizes = ['G', 'P', 'GG', 'M', 'PP']; - const sorted = [...sizes].sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - expect(sorted).toEqual(['PP', 'P', 'M', 'G', 'GG']); - }); - - it('numeric sizes order correctly', () => { - const sizes = ['42', '38', '40', '36', '44']; - const sorted = [...sizes].sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - expect(sorted).toEqual(['36', '38', '40', '42', '44']); - }); - - it('volume sizes parsed numerically by parseFloat', () => { - const sizes = ['500ml', '200ml', '1L', '100ml', '350ml']; - const sorted = [...sizes].sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - // parseFloat('100ml')=100, parseFloat('1L')=1, so 1L sorts first numerically - expect(sorted).toEqual(['1L', '100ml', '200ml', '350ml', '500ml']); - }); - - it('mixed clothing and numeric sizes', () => { - const sizes = ['42', 'G', 'M', '38']; - const sorted = [...sizes].sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - expect(sorted).toEqual(['M', 'G', '38', '42']); - }); - - it('unknown sizes go to end', () => { - const sizes = ['Custom', 'M', 'G', 'Especial']; - const sorted = [...sizes].sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - expect(sorted[0]).toBe('M'); - expect(sorted[1]).toBe('G'); - }); - - it('case insensitive ordering', () => { - const sizes = ['m', 'g', 'P']; - const sorted = [...sizes].sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - expect(sorted).toEqual(['P', 'm', 'g']); // P < M < G in SIZE_ORDER - }); - - it('extracts unique sizes from products', () => { - const products = [ - { variations: [{ size_code: 'P' }, { size_code: 'M' }, { size_code: 'G' }] }, - { variations: [{ size_code: 'M' }, { size_code: 'G' }, { size_code: 'GG' }] }, - { variations: [{ size_code: null }] }, - { variations: [] }, - ]; - const sizeSet = new Set(); - products.forEach(p => { - p.variations?.forEach(v => { - if (v.size_code) sizeSet.add(v.size_code); - }); - }); - const sizes = Array.from(sizeSet).sort((a, b) => getSizeOrder(a) - getSizeOrder(b)); - expect(sizes).toEqual(['P', 'M', 'G', 'GG']); - }); - - it('filters products by selected sizes', () => { - const products = [ - { id: '1', name: 'Camiseta', variations: [ - { size_code: 'P' }, { size_code: 'M' }, { size_code: 'G' }, - ]}, - { id: '2', name: 'Polo', variations: [ - { size_code: 'M' }, { size_code: 'GG' }, - ]}, - { id: '3', name: 'Caneta', variations: [] }, - ]; - const selectedSizes = ['GG']; - const filtered = products.filter(p => - p.variations.some(v => v.size_code && selectedSizes.includes(v.size_code)) - ); - expect(filtered).toHaveLength(1); - expect(filtered[0].id).toBe('2'); - }); - - it('empty size selection returns all products', () => { - const products = [{ id: '1' }, { id: '2' }]; - const selectedSizes: string[] = []; - // No filter applied - expect(selectedSizes.length === 0 ? products : []).toEqual(products); - }); - - it('handles product with no variations gracefully', () => { - const products = [ - { variations: undefined as any }, - { variations: null as any }, - ]; - const sizeSet = new Set(); - products.forEach(p => { - p.variations?.forEach?.((v: any) => { - if (v?.size_code) sizeSet.add(v.size_code); - }); - }); - expect(sizeSet.size).toBe(0); - }); - - it('search filter within size options', () => { - const availableSizes = ['PP', 'P', 'M', 'G', 'GG', 'XG', '42', '44']; - const search = 'g'; - const filtered = availableSizes.filter(s => s.toLowerCase().includes(search.toLowerCase())); - expect(filtered).toEqual(['G', 'GG', 'XG']); - }); - - it('search with no results', () => { - const availableSizes = ['P', 'M', 'G']; - const filtered = availableSizes.filter(s => s.toLowerCase().includes('xyz')); - expect(filtered).toHaveLength(0); - }); -}); - -// ============ 2.3 — Gender Badge Logic ============ -describe('Phase 2.3 — GenderBadge rendering rules', () => { - const GENDER_CONFIG: Record = { - masculino: { label: 'Masc.', className: 'bg-blue-500/10 text-blue-700 border-blue-200' }, - feminino: { label: 'Fem.', className: 'bg-pink-500/10 text-pink-700 border-pink-200' }, - infantil: { label: 'Infantil', className: 'bg-amber-500/10 text-amber-700 border-amber-200' }, - unissex: { label: 'Unissex', className: 'bg-violet-500/10 text-violet-700 border-violet-200' }, - }; - - function getGenderLabel(gender: string | null | undefined): string | null { - if (!gender) return null; - const key = gender.toLowerCase().trim(); - return GENDER_CONFIG[key]?.label ?? null; - } - - it('returns "Masc." for masculino', () => { - expect(getGenderLabel('masculino')).toBe('Masc.'); - }); - - it('returns "Fem." for feminino', () => { - expect(getGenderLabel('feminino')).toBe('Fem.'); - }); - - it('returns "Infantil" for infantil', () => { - expect(getGenderLabel('infantil')).toBe('Infantil'); - }); - - it('returns "Unissex" for unissex', () => { - expect(getGenderLabel('unissex')).toBe('Unissex'); - }); - - it('returns null for null gender', () => { - expect(getGenderLabel(null)).toBeNull(); - }); - - it('returns null for undefined gender', () => { - expect(getGenderLabel(undefined)).toBeNull(); - }); - - it('returns null for empty string', () => { - expect(getGenderLabel('')).toBeNull(); - }); - - it('returns null for unknown gender', () => { - expect(getGenderLabel('outro')).toBeNull(); - expect(getGenderLabel('agender')).toBeNull(); - }); - - it('handles uppercase input', () => { - expect(getGenderLabel('MASCULINO')).toBe('Masc.'); - expect(getGenderLabel('FEMININO')).toBe('Fem.'); - }); - - it('handles mixed case input', () => { - expect(getGenderLabel('Masculino')).toBe('Masc.'); - expect(getGenderLabel('InFanTiL')).toBe('Infantil'); - }); - - it('handles whitespace', () => { - expect(getGenderLabel(' masculino ')).toBe('Masc.'); - }); - - it('each gender has unique color scheme', () => { - const classNames = Object.values(GENDER_CONFIG).map(c => c.className); - const unique = new Set(classNames); - expect(unique.size).toBe(classNames.length); - }); - - it('all configs have label and className', () => { - Object.values(GENDER_CONFIG).forEach(config => { - expect(config.label).toBeTruthy(); - expect(config.className).toBeTruthy(); - }); - }); - - it('badge sizes sm and md have different text classes', () => { - const smClass = 'text-[10px] px-1.5 py-0'; - const mdClass = 'text-xs px-2 py-0.5'; - expect(smClass).not.toBe(mdClass); - }); -}); diff --git a/tests/e2e/phase3-detail-experience.test.ts b/tests/e2e/phase3-detail-experience.test.ts deleted file mode 100644 index ed1d73f7a..000000000 --- a/tests/e2e/phase3-detail-experience.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -/** - * Phase 3: Detail Experience — Size Selector, Variant Grid Matrix, Stock by Combination - * Validates size selection logic, grid building, stock resolution, and color×size matrix. - */ -import { describe, it, expect } from 'vitest'; - -// ============ SIZE_ORDER Constants ============ -const SIZE_ORDER = ['PP', 'P', 'M', 'G', 'GG', 'XG', 'XXG', 'EG', 'EGG', 'XS', 'S', 'L', 'XL', 'XXL', '2XL', '3XL']; - -function getSizeOrder(code: string): number { - const idx = SIZE_ORDER.indexOf(code.toUpperCase()); - return idx >= 0 ? idx : 999; -} - -// ============ 3.1 — ProductSizeSelector Logic ============ -describe('Phase 3.1 — ProductSizeSelector', () => { - interface SizeOption { - code: string; - stock: number; - variantIds: string[]; - } - - function buildSizeOptions(variations: Array<{ id: string; size_code?: string | null; stock?: number }>): SizeOption[] { - const sizeMap = new Map(); - for (const v of variations) { - if (!v.size_code) continue; - const existing = sizeMap.get(v.size_code); - if (existing) { - existing.stock += Math.max(0, v.stock ?? 0); - existing.variantIds.push(v.id); - } else { - sizeMap.set(v.size_code, { - code: v.size_code, - stock: Math.max(0, v.stock ?? 0), - variantIds: [v.id], - }); - } - } - return Array.from(sizeMap.values()).sort( - (a, b) => getSizeOrder(a.code) - getSizeOrder(b.code) || a.code.localeCompare(b.code) - ); - } - - it('builds size options from variations', () => { - const variations = [ - { id: 'v1', size_code: 'P', stock: 50 }, - { id: 'v2', size_code: 'M', stock: 100 }, - { id: 'v3', size_code: 'G', stock: 200 }, - ]; - const options = buildSizeOptions(variations); - expect(options).toHaveLength(3); - expect(options[0].code).toBe('P'); - expect(options[1].code).toBe('M'); - expect(options[2].code).toBe('G'); - }); - - it('aggregates stock across same size_code', () => { - const variations = [ - { id: 'v1', size_code: 'M', stock: 50 }, // Preto M - { id: 'v2', size_code: 'M', stock: 75 }, // Azul M - { id: 'v3', size_code: 'M', stock: 25 }, // Branco M - ]; - const options = buildSizeOptions(variations); - expect(options).toHaveLength(1); - expect(options[0].stock).toBe(150); - expect(options[0].variantIds).toHaveLength(3); - }); - - it('excludes null/undefined size_codes', () => { - const variations = [ - { id: 'v1', size_code: 'M', stock: 50 }, - { id: 'v2', size_code: null, stock: 100 }, - { id: 'v3', size_code: undefined, stock: 200 }, - ]; - const options = buildSizeOptions(variations); - expect(options).toHaveLength(1); - expect(options[0].code).toBe('M'); - }); - - it('handles empty variations array', () => { - expect(buildSizeOptions([])).toHaveLength(0); - }); - - it('handles all null size_codes', () => { - const variations = [ - { id: 'v1', size_code: null, stock: 100 }, - { id: 'v2', size_code: null, stock: 200 }, - ]; - expect(buildSizeOptions(variations)).toHaveLength(0); - }); - - it('sorts by SIZE_ORDER priority', () => { - const variations = [ - { id: 'v1', size_code: 'GG', stock: 10 }, - { id: 'v2', size_code: 'PP', stock: 10 }, - { id: 'v3', size_code: 'G', stock: 10 }, - { id: 'v4', size_code: 'P', stock: 10 }, - { id: 'v5', size_code: 'M', stock: 10 }, - ]; - const options = buildSizeOptions(variations); - expect(options.map(o => o.code)).toEqual(['PP', 'P', 'M', 'G', 'GG']); - }); - - it('handles zero stock (should show as disabled)', () => { - const variations = [ - { id: 'v1', size_code: 'M', stock: 0 }, - ]; - const options = buildSizeOptions(variations); - expect(options[0].stock).toBe(0); - }); - - it('handles negative stock (treats as 0)', () => { - const variations = [ - { id: 'v1', size_code: 'M', stock: -5 }, - ]; - const options = buildSizeOptions(variations); - expect(options[0].stock).toBe(0); - }); - - it('toggle selection: select then deselect', () => { - let selected: string | null = null; - selected = 'M'; - expect(selected).toBe('M'); - selected = selected === 'M' ? null : 'M'; - expect(selected).toBeNull(); - }); - - it('switch between sizes', () => { - let selected: string | null = 'M'; - selected = 'G'; - expect(selected).toBe('G'); - }); -}); - -// ============ 3.2 — VariantGridMatrix Logic ============ -describe('Phase 3.2 — VariantGridMatrix grid building', () => { - interface GridItem { - id: string; - color_name: string; - color_hex: string; - size_code?: string | null; - stock: number; - sku?: string; - price?: number | null; - } - - const GRID_SIZE_ORDER = [ - 'PP', 'P', 'M', 'G', 'GG', 'XG', 'XXG', 'EG', 'EGG', - 'XS', 'S', 'L', 'XL', 'XXL', '2XL', '3XL', - '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', - ]; - - function getGridSizeOrder(code: string): number { - const upper = code.toUpperCase().trim(); - const idx = GRID_SIZE_ORDER.indexOf(upper); - if (idx >= 0) return idx; - const num = parseFloat(upper); - if (!isNaN(num)) return 1000 + num; - return 2000; - } - - function buildGrid(variants: GridItem[]) { - const hasSizes = variants.some(v => v.size_code); - const colors = [...new Set(variants.map(v => v.color_name))].sort(); - const sizes = hasSizes - ? [...new Set(variants.filter(v => v.size_code).map(v => v.size_code!))].sort((a, b) => getGridSizeOrder(a) - getGridSizeOrder(b)) - : []; - - const matrix = new Map>(); - variants.forEach(v => { - if (!matrix.has(v.color_name)) matrix.set(v.color_name, new Map()); - const key = v.size_code || '__no_size__'; - matrix.get(v.color_name)!.set(key, v); - }); - - return { hasSizes, colors, sizes, matrix }; - } - - const sampleVariants: GridItem[] = [ - { id: 'v1', color_name: 'Preto', color_hex: '#000', size_code: 'P', stock: 50, sku: 'CAM-BLK-P' }, - { id: 'v2', color_name: 'Preto', color_hex: '#000', size_code: 'M', stock: 100, sku: 'CAM-BLK-M' }, - { id: 'v3', color_name: 'Preto', color_hex: '#000', size_code: 'G', stock: 0, sku: 'CAM-BLK-G' }, - { id: 'v4', color_name: 'Azul', color_hex: '#00F', size_code: 'P', stock: 30, sku: 'CAM-BLU-P' }, - { id: 'v5', color_name: 'Azul', color_hex: '#00F', size_code: 'M', stock: 80, sku: 'CAM-BLU-M' }, - { id: 'v6', color_name: 'Azul', color_hex: '#00F', size_code: 'G', stock: 120, sku: 'CAM-BLU-G' }, - { id: 'v7', color_name: 'Branco', color_hex: '#FFF', size_code: 'P', stock: 0, sku: 'CAM-WHT-P' }, - { id: 'v8', color_name: 'Branco', color_hex: '#FFF', size_code: 'M', stock: 40, sku: 'CAM-WHT-M' }, - ]; - - it('detects hasSizes correctly', () => { - const grid = buildGrid(sampleVariants); - expect(grid.hasSizes).toBe(true); - }); - - it('detects no sizes', () => { - const noSizeVariants: GridItem[] = [ - { id: 'v1', color_name: 'Preto', color_hex: '#000', stock: 100 }, - { id: 'v2', color_name: 'Azul', color_hex: '#00F', stock: 50 }, - ]; - const grid = buildGrid(noSizeVariants); - expect(grid.hasSizes).toBe(false); - expect(grid.sizes).toHaveLength(0); - }); - - it('extracts all unique colors', () => { - const grid = buildGrid(sampleVariants); - expect(grid.colors).toHaveLength(3); - expect(grid.colors).toContain('Preto'); - expect(grid.colors).toContain('Azul'); - expect(grid.colors).toContain('Branco'); - }); - - it('extracts and sorts unique sizes', () => { - const grid = buildGrid(sampleVariants); - expect(grid.sizes).toEqual(['P', 'M', 'G']); - }); - - it('builds correct matrix lookups', () => { - const grid = buildGrid(sampleVariants); - expect(grid.matrix.get('Preto')?.get('M')?.stock).toBe(100); - expect(grid.matrix.get('Azul')?.get('G')?.stock).toBe(120); - expect(grid.matrix.get('Branco')?.get('P')?.stock).toBe(0); - }); - - it('handles sparse matrix (missing combinations)', () => { - const grid = buildGrid(sampleVariants); - // Branco doesn't have 'G' - expect(grid.matrix.get('Branco')?.get('G')).toBeUndefined(); - }); - - it('handles single color, multiple sizes', () => { - const single: GridItem[] = [ - { id: 'v1', color_name: 'Preto', color_hex: '#000', size_code: 'PP', stock: 10 }, - { id: 'v2', color_name: 'Preto', color_hex: '#000', size_code: 'P', stock: 20 }, - { id: 'v3', color_name: 'Preto', color_hex: '#000', size_code: 'M', stock: 30 }, - ]; - const grid = buildGrid(single); - expect(grid.colors).toHaveLength(1); - expect(grid.sizes).toHaveLength(3); - expect(grid.sizes).toEqual(['PP', 'P', 'M']); - }); - - it('handles many colors, single size', () => { - const single: GridItem[] = [ - { id: 'v1', color_name: 'Preto', color_hex: '#000', size_code: 'M', stock: 10 }, - { id: 'v2', color_name: 'Azul', color_hex: '#00F', size_code: 'M', stock: 20 }, - { id: 'v3', color_name: 'Verde', color_hex: '#0F0', size_code: 'M', stock: 30 }, - ]; - const grid = buildGrid(single); - expect(grid.colors).toHaveLength(3); - expect(grid.sizes).toHaveLength(1); - }); - - it('handles empty variants array', () => { - const grid = buildGrid([]); - expect(grid.hasSizes).toBe(false); - expect(grid.colors).toHaveLength(0); - expect(grid.sizes).toHaveLength(0); - }); -}); - -// ============ 3.3 — Stock by Combination ============ -describe('Phase 3.3 — Stock Resolution per Combination', () => { - function formatStock(stock: number): string { - if (stock >= 1000) return `${(stock / 1000).toFixed(1)}k`; - return stock.toLocaleString('pt-BR'); - } - - function stockColor(stock: number): string { - if (stock === 0) return 'text-destructive'; - if (stock < 100) return 'text-warning'; - return 'text-success'; - } - - function isLightColor(hex?: string | null): boolean { - if (!hex) return true; - const c = hex.replace('#', ''); - if (c.length < 6) return true; - const r = parseInt(c.substring(0, 2), 16); - const g = parseInt(c.substring(2, 4), 16); - const b = parseInt(c.substring(4, 6), 16); - return (r * 299 + g * 587 + b * 114) / 1000 > 160; - } - - it('formats stock < 1000', () => { - expect(formatStock(500)).toBe('500'); - expect(formatStock(0)).toBe('0'); - expect(formatStock(999)).toBe('999'); - }); - - it('formats stock >= 1000 with k suffix', () => { - expect(formatStock(1000)).toBe('1.0k'); - expect(formatStock(1500)).toBe('1.5k'); - expect(formatStock(10000)).toBe('10.0k'); - }); - - it('stock color: destructive for 0', () => { - expect(stockColor(0)).toBe('text-destructive'); - }); - - it('stock color: warning for < 100', () => { - expect(stockColor(1)).toBe('text-warning'); - expect(stockColor(50)).toBe('text-warning'); - expect(stockColor(99)).toBe('text-warning'); - }); - - it('stock color: success for >= 100', () => { - expect(stockColor(100)).toBe('text-success'); - expect(stockColor(5000)).toBe('text-success'); - }); - - it('isLightColor: black is dark', () => { - expect(isLightColor('#000000')).toBe(false); - }); - - it('isLightColor: white is light', () => { - expect(isLightColor('#FFFFFF')).toBe(true); - }); - - it('isLightColor: yellow is light', () => { - expect(isLightColor('#FFFF00')).toBe(true); - }); - - it('isLightColor: dark blue is dark', () => { - expect(isLightColor('#000080')).toBe(false); - }); - - it('isLightColor: null defaults to true', () => { - expect(isLightColor(null)).toBe(true); - expect(isLightColor(undefined)).toBe(true); - }); - - it('isLightColor: short hex defaults to true', () => { - expect(isLightColor('#000')).toBe(true); // less than 6 chars - }); - - it('resolves stock for specific color+size combination', () => { - const variants = [ - { color: 'Preto', size: 'M', stock: 150 }, - { color: 'Preto', size: 'G', stock: 0 }, - { color: 'Azul', size: 'M', stock: 80 }, - ]; - const lookup = (color: string, size: string) => - variants.find(v => v.color === color && v.size === size)?.stock ?? -1; - - expect(lookup('Preto', 'M')).toBe(150); - expect(lookup('Preto', 'G')).toBe(0); - expect(lookup('Azul', 'M')).toBe(80); - expect(lookup('Azul', 'G')).toBe(-1); // not found - }); -}); - -// ============ 3.4 — Selection State Management ============ -describe('Phase 3.4 — Selection state in grid', () => { - it('single selection mode', () => { - let selectedId: string | null = null; - - selectedId = 'v1'; - expect(selectedId).toBe('v1'); - - selectedId = 'v2'; - expect(selectedId).toBe('v2'); - - selectedId = null; - expect(selectedId).toBeNull(); - }); - - it('admin multi-selection mode', () => { - const selected = new Set(); - - selected.add('v1'); - selected.add('v2'); - expect(selected.size).toBe(2); - - selected.delete('v1'); - expect(selected.size).toBe(1); - expect(selected.has('v2')).toBe(true); - - selected.clear(); - expect(selected.size).toBe(0); - }); - - it('select all / deselect all', () => { - const allIds = ['v1', 'v2', 'v3', 'v4', 'v5']; - const selected = new Set(); - - // Select all - allIds.forEach(id => selected.add(id)); - expect(selected.size).toBe(5); - - // Deselect all - selected.clear(); - expect(selected.size).toBe(0); - }); - - it('toggle selection', () => { - const selected = new Set(); - - const toggle = (id: string) => { - if (selected.has(id)) selected.delete(id); - else selected.add(id); - }; - - toggle('v1'); - expect(selected.has('v1')).toBe(true); - toggle('v1'); - expect(selected.has('v1')).toBe(false); - }); -}); diff --git a/tests/e2e/phase4-admin-grid.test.ts b/tests/e2e/phase4-admin-grid.test.ts deleted file mode 100644 index adaac709b..000000000 --- a/tests/e2e/phase4-admin-grid.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Phase 4: Admin — Variant Grid Matrix, Bulk Actions, Variation Axes - * Validates bulk operations, axis configuration, and admin-mode grid behavior. - */ -import { describe, it, expect } from 'vitest'; - -// ============ 4.1 — Variation Axes Configuration ============ -describe('Phase 4.1 — Variation Axes Config', () => { - interface AxisConfig { - axis: string; - values: string[]; - enabled: boolean; - } - - function detectAxes(variants: Array<{ - color_name?: string; - size_code?: string | null; - gender?: string | null; - }>): string[] { - const axes: string[] = []; - if (variants.some(v => v.color_name)) axes.push('cor'); - if (variants.some(v => v.size_code)) axes.push('tamanho'); - if (variants.some(v => v.gender)) axes.push('genero'); - return axes; - } - - it('detects color-only axes', () => { - const variants = [ - { color_name: 'Preto' }, - { color_name: 'Azul' }, - ]; - expect(detectAxes(variants)).toEqual(['cor']); - }); - - it('detects color + size axes', () => { - const variants = [ - { color_name: 'Preto', size_code: 'M' }, - { color_name: 'Azul', size_code: 'G' }, - ]; - expect(detectAxes(variants)).toEqual(['cor', 'tamanho']); - }); - - it('detects all three axes', () => { - const variants = [ - { color_name: 'Preto', size_code: 'M', gender: 'masculino' }, - ]; - expect(detectAxes(variants)).toEqual(['cor', 'tamanho', 'genero']); - }); - - it('detects no axes from empty array', () => { - expect(detectAxes([])).toEqual([]); - }); - - it('handles mixed null/defined fields', () => { - const variants = [ - { color_name: 'Preto', size_code: null }, - { color_name: 'Azul', size_code: 'M' }, - ]; - expect(detectAxes(variants)).toEqual(['cor', 'tamanho']); - }); - - it('axis config with standard clothing sizes', () => { - const config: AxisConfig = { - axis: 'tamanho', - values: ['PP', 'P', 'M', 'G', 'GG', 'XG'], - enabled: true, - }; - expect(config.values).toHaveLength(6); - expect(config.enabled).toBe(true); - }); - - it('axis config with shoe sizes', () => { - const config: AxisConfig = { - axis: 'tamanho', - values: ['36', '37', '38', '39', '40', '41', '42', '43', '44'], - enabled: true, - }; - expect(config.values).toHaveLength(9); - }); - - it('axis config with volumes', () => { - const config: AxisConfig = { - axis: 'capacidade', - values: ['100ml', '200ml', '350ml', '500ml', '1L'], - enabled: true, - }; - expect(config.values).toHaveLength(5); - }); - - it('disabled axis is ignored', () => { - const config: AxisConfig = { - axis: 'genero', - values: ['masculino', 'feminino'], - enabled: false, - }; - // Disabled axes should be skipped in grid generation - expect(config.enabled).toBe(false); - }); - - it('calculates total combinations (cartesian product)', () => { - const colors = ['Preto', 'Azul', 'Branco']; - const sizes = ['P', 'M', 'G', 'GG']; - const total = colors.length * sizes.length; - expect(total).toBe(12); - }); - - it('calculates combinations with single axis', () => { - const colors = ['Preto', 'Azul']; - expect(colors.length).toBe(2); - }); - - it('handles very large combination space', () => { - const colors = Array.from({ length: 20 }, (_, i) => `Color${i}`); - const sizes = Array.from({ length: 10 }, (_, i) => `Size${i}`); - expect(colors.length * sizes.length).toBe(200); - }); -}); - -// ============ 4.2 — Bulk Actions Logic ============ -describe('Phase 4.2 — Bulk Actions', () => { - interface BulkAction { - type: 'toggle_active' | 'update_stock'; - variantIds: string[]; - value?: boolean | number; - } - - function validateBulkAction(action: BulkAction): { valid: boolean; error?: string } { - if (action.variantIds.length === 0) { - return { valid: false, error: 'Nenhuma variação selecionada' }; - } - if (action.type === 'update_stock') { - if (typeof action.value !== 'number') return { valid: false, error: 'Valor de estoque inválido' }; - if (action.value < 0) return { valid: false, error: 'Estoque não pode ser negativo' }; - if (!Number.isInteger(action.value)) return { valid: false, error: 'Estoque deve ser inteiro' }; - } - if (action.type === 'toggle_active') { - if (typeof action.value !== 'boolean') return { valid: false, error: 'Valor de ativação inválido' }; - } - return { valid: true }; - } - - it('valid toggle_active action', () => { - const result = validateBulkAction({ - type: 'toggle_active', variantIds: ['v1', 'v2'], value: true, - }); - expect(result.valid).toBe(true); - }); - - it('valid update_stock action', () => { - const result = validateBulkAction({ - type: 'update_stock', variantIds: ['v1'], value: 100, - }); - expect(result.valid).toBe(true); - }); - - it('rejects empty variantIds', () => { - const result = validateBulkAction({ - type: 'toggle_active', variantIds: [], value: true, - }); - expect(result.valid).toBe(false); - expect(result.error).toContain('Nenhuma variação'); - }); - - it('rejects negative stock', () => { - const result = validateBulkAction({ - type: 'update_stock', variantIds: ['v1'], value: -10, - }); - expect(result.valid).toBe(false); - }); - - it('rejects decimal stock', () => { - const result = validateBulkAction({ - type: 'update_stock', variantIds: ['v1'], value: 10.5, - }); - expect(result.valid).toBe(false); - }); - - it('accepts zero stock', () => { - const result = validateBulkAction({ - type: 'update_stock', variantIds: ['v1', 'v2', 'v3'], value: 0, - }); - expect(result.valid).toBe(true); - }); - - it('accepts large stock value', () => { - const result = validateBulkAction({ - type: 'update_stock', variantIds: ['v1'], value: 999_999, - }); - expect(result.valid).toBe(true); - }); - - it('rejects missing value for update_stock', () => { - const result = validateBulkAction({ - type: 'update_stock', variantIds: ['v1'], - }); - expect(result.valid).toBe(false); - }); - - it('rejects missing value for toggle_active', () => { - const result = validateBulkAction({ - type: 'toggle_active', variantIds: ['v1'], - }); - expect(result.valid).toBe(false); - }); - - it('deactivate all variants', () => { - const action: BulkAction = { - type: 'toggle_active', - variantIds: ['v1', 'v2', 'v3', 'v4', 'v5'], - value: false, - }; - expect(action.variantIds).toHaveLength(5); - expect(action.value).toBe(false); - }); - - it('activate subset of variants', () => { - const action: BulkAction = { - type: 'toggle_active', - variantIds: ['v2', 'v4'], - value: true, - }; - expect(action.variantIds).toHaveLength(2); - expect(action.value).toBe(true); - }); -}); - -// ============ 4.3 — Admin Grid Selection ============ -describe('Phase 4.3 — Admin grid multi-selection', () => { - it('select individual cells', () => { - const selected = new Set(); - selected.add('v1'); - selected.add('v3'); - selected.add('v5'); - expect(selected.size).toBe(3); - }); - - it('select all in a row (color)', () => { - const rowVariants = ['v1', 'v2', 'v3']; // Preto: P, M, G - const selected = new Set(rowVariants); - expect(selected.size).toBe(3); - rowVariants.forEach(id => expect(selected.has(id)).toBe(true)); - }); - - it('select all in a column (size)', () => { - const colVariants = ['v1', 'v4', 'v7']; // size M: Preto, Azul, Branco - const selected = new Set(colVariants); - expect(selected.size).toBe(3); - }); - - it('select all button', () => { - const allVariants = ['v1', 'v2', 'v3', 'v4', 'v5', 'v6', 'v7', 'v8']; - const selected = new Set(allVariants); - expect(selected.size).toBe(8); - }); - - it('deselect all after selection', () => { - const selected = new Set(['v1', 'v2', 'v3']); - selected.clear(); - expect(selected.size).toBe(0); - }); - - it('badge shows count correctly', () => { - const selectedCount = 3; - const totalCount = 12; - const label = `${selectedCount}/${totalCount} selecionados`; - expect(label).toBe('3/12 selecionados'); - }); - - it('stock input validation: empty string', () => { - const value = ''; - const num = parseInt(value, 10); - expect(isNaN(num)).toBe(true); - }); - - it('stock input validation: valid number', () => { - const value = '100'; - const num = parseInt(value, 10); - expect(num).toBe(100); - expect(isNaN(num)).toBe(false); - }); - - it('stock input validation: negative rejected', () => { - const value = '-5'; - const num = parseInt(value, 10); - expect(num < 0).toBe(true); - }); - - it('stock input validation: letters rejected', () => { - const value = 'abc'; - const num = parseInt(value, 10); - expect(isNaN(num)).toBe(true); - }); -}); diff --git a/tests/e2e/phase5-propagation.test.ts b/tests/e2e/phase5-propagation.test.ts deleted file mode 100644 index 6e18f1e3e..000000000 --- a/tests/e2e/phase5-propagation.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -/** - * Phase 5: Propagation — Quotes, Kit Builder, Simulator, Export - * Validates size_code propagation to all dependent modules. - */ -import { describe, it, expect } from 'vitest'; - -// ============ 5.1 — Quotes with Size ============ -describe('Phase 5.1 — Quote Items with size_code', () => { - interface QuoteItem { - id: string; - product_name: string; - product_sku: string | null; - quantity: number; - unit_price: number; - color_name?: string | null; - color_hex?: string | null; - size_code?: string | null; - subtotal?: number | null; - } - - it('quote item with color and size', () => { - const item: QuoteItem = { - id: 'qi1', product_name: 'Camiseta', product_sku: 'CAM-001', - quantity: 100, unit_price: 35, - color_name: 'Preto', color_hex: '#000000', size_code: 'M', - subtotal: 3500, - }; - expect(item.size_code).toBe('M'); - expect(item.subtotal).toBe(3500); - }); - - it('quote item without size (non-apparel)', () => { - const item: QuoteItem = { - id: 'qi2', product_name: 'Caneta BIC', product_sku: 'CAN-001', - quantity: 500, unit_price: 5.50, - color_name: 'Azul', size_code: null, - }; - expect(item.size_code).toBeNull(); - }); - - it('quote item subtotal calculation', () => { - const item: QuoteItem = { - id: 'qi3', product_name: 'Polo', product_sku: 'POL-001', - quantity: 50, unit_price: 42.90, - }; - const subtotal = item.quantity * item.unit_price; - expect(subtotal).toBe(2145); - }); - - it('multiple items with different sizes', () => { - const items: QuoteItem[] = [ - { id: 'qi1', product_name: 'Camiseta', product_sku: 'CAM-001', quantity: 50, unit_price: 35, size_code: 'P', color_name: 'Preto' }, - { id: 'qi2', product_name: 'Camiseta', product_sku: 'CAM-001', quantity: 100, unit_price: 35, size_code: 'M', color_name: 'Preto' }, - { id: 'qi3', product_name: 'Camiseta', product_sku: 'CAM-001', quantity: 75, unit_price: 35, size_code: 'G', color_name: 'Preto' }, - ]; - const totalQty = items.reduce((sum, i) => sum + i.quantity, 0); - expect(totalQty).toBe(225); - expect(items.map(i => i.size_code)).toEqual(['P', 'M', 'G']); - }); - - it('size code persists in quote item display', () => { - const item: QuoteItem = { - id: 'qi4', product_name: 'Polo', product_sku: 'POL-001', - quantity: 100, unit_price: 42, - color_name: 'Branco', color_hex: '#FFFFFF', size_code: 'GG', - }; - const displayText = `${item.product_name} - ${item.color_name} / ${item.size_code}`; - expect(displayText).toBe('Polo - Branco / GG'); - }); - - it('handles quote item without color or size', () => { - const item: QuoteItem = { - id: 'qi5', product_name: 'Adesivo', product_sku: 'ADE-001', - quantity: 1000, unit_price: 0.50, - }; - expect(item.color_name).toBeUndefined(); - expect(item.size_code).toBeUndefined(); - }); -}); - -// ============ 5.2 — Kit Builder with Size ============ -describe('Phase 5.2 — Kit Builder size propagation', () => { - interface KitItem { - id: string; - name: string; - price: number; - quantity: number; - selectedColor?: { name: string; hex?: string }; - selectedSize?: string; - } - - interface VariantSelectionData { - color: { name: string; hex?: string }; - size?: string; - sku?: string; - imageUrl?: string | null; - price?: number; - } - - it('variant selection includes size', () => { - const selection: VariantSelectionData = { - color: { name: 'Preto', hex: '#000' }, - size: 'M', - sku: 'CAM-001-BLK-M', - price: 35.90, - }; - expect(selection.size).toBe('M'); - }); - - it('variant selection without size', () => { - const selection: VariantSelectionData = { - color: { name: 'Azul', hex: '#00F' }, - sku: 'CAN-001-BLU', - }; - expect(selection.size).toBeUndefined(); - }); - - it('kit item updated with size', () => { - const item: KitItem = { - id: '1', name: 'Camiseta', price: 35, quantity: 1, - selectedColor: { name: 'Preto', hex: '#000' }, - }; - const updated = { ...item, selectedSize: 'G' }; - expect(updated.selectedSize).toBe('G'); - expect(updated.selectedColor!.name).toBe('Preto'); - }); - - it('kit total unchanged by size selection', () => { - const items: KitItem[] = [ - { id: '1', name: 'Camiseta', price: 35, quantity: 1, selectedSize: 'M' }, - { id: '2', name: 'Caneta', price: 5, quantity: 1 }, - { id: '3', name: 'Squeeze', price: 25, quantity: 1 }, - ]; - const total = items.reduce((s, i) => s + i.price * i.quantity, 0); - expect(total).toBe(65); - }); - - it('size ordering in kit builder', () => { - const SIZE_ORDER = ['PP', 'P', 'M', 'G', 'GG', 'XG', '2XG', '3XG', 'EG', 'EGG']; - function sizeSort(a: string, b: string): number { - const ia = SIZE_ORDER.indexOf(a.toUpperCase()); - const ib = SIZE_ORDER.indexOf(b.toUpperCase()); - if (ia !== -1 && ib !== -1) return ia - ib; - if (ia !== -1) return -1; - if (ib !== -1) return 1; - return a.localeCompare(b, 'pt-BR'); - } - - const sizes = ['GG', 'P', 'M', 'XG', 'G']; - const sorted = [...sizes].sort(sizeSort); - expect(sorted).toEqual(['P', 'M', 'G', 'GG', 'XG']); - }); - - it('grouping variants by color with size sub-options', () => { - const variants = [ - { id: 'v1', color_name: 'Preto', size_code: 'P' }, - { id: 'v2', color_name: 'Preto', size_code: 'M' }, - { id: 'v3', color_name: 'Preto', size_code: 'G' }, - { id: 'v4', color_name: 'Azul', size_code: 'P' }, - { id: 'v5', color_name: 'Azul', size_code: 'M' }, - ]; - const grouped = new Map(); - variants.forEach(v => { - const key = v.color_name; - if (!grouped.has(key)) grouped.set(key, []); - grouped.get(key)!.push(v); - }); - expect(grouped.get('Preto')).toHaveLength(3); - expect(grouped.get('Azul')).toHaveLength(2); - }); - - it('badge displays size when selected', () => { - const item: KitItem = { - id: '1', name: 'Polo', price: 42, quantity: 1, - selectedColor: { name: 'Branco' }, selectedSize: 'GG', - }; - const badgeText = item.selectedSize ? `Tam: ${item.selectedSize}` : ''; - expect(badgeText).toBe('Tam: GG'); - }); - - it('no badge when no size selected', () => { - const item: KitItem = { - id: '1', name: 'Caneta', price: 5, quantity: 1, - }; - const badgeText = item.selectedSize ? `Tam: ${item.selectedSize}` : ''; - expect(badgeText).toBe(''); - }); -}); - -// ============ 5.3 — Simulator with Size Pricing ============ -describe('Phase 5.3 — Simulator size differentiation', () => { - interface ProductVariant { - code: string; - name: string; - hex?: string; - stock?: number; - size_code?: string | null; - sale_price?: number | null; - } - - function buildVariantsFromDb(dbRecords: Array<{ - id: string; - color_name: string | null; - color_hex: string | null; - size_code: string | null; - stock_quantity: number | null; - sale_price: number | null; - }>): ProductVariant[] { - return dbRecords - .filter(v => v.color_name) - .map(v => ({ - code: v.id, - name: v.color_name || 'Padrão', - hex: v.color_hex || undefined, - stock: v.stock_quantity ?? undefined, - size_code: v.size_code, - sale_price: v.sale_price, - })); - } - - it('builds variants from DB records', () => { - const dbRecords = [ - { id: 'v1', color_name: 'Preto', color_hex: '#000', size_code: 'M', stock_quantity: 100, sale_price: 35.90 }, - { id: 'v2', color_name: 'Azul', color_hex: '#00F', size_code: 'G', stock_quantity: 50, sale_price: 38.00 }, - ]; - const variants = buildVariantsFromDb(dbRecords); - expect(variants).toHaveLength(2); - expect(variants[0].size_code).toBe('M'); - expect(variants[1].sale_price).toBe(38.00); - }); - - it('filters out records without color_name', () => { - const dbRecords = [ - { id: 'v1', color_name: null, color_hex: null, size_code: 'M', stock_quantity: 100, sale_price: 35 }, - { id: 'v2', color_name: 'Preto', color_hex: '#000', size_code: 'M', stock_quantity: 100, sale_price: 35 }, - ]; - const variants = buildVariantsFromDb(dbRecords); - expect(variants).toHaveLength(1); - expect(variants[0].name).toBe('Preto'); - }); - - it('detects hasSizes from variants', () => { - const withSizes: ProductVariant[] = [ - { code: 'v1', name: 'Preto', size_code: 'M' }, - { code: 'v2', name: 'Azul', size_code: 'G' }, - ]; - expect(withSizes.some(v => v.size_code)).toBe(true); - - const withoutSizes: ProductVariant[] = [ - { code: 'v1', name: 'Preto' }, - { code: 'v2', name: 'Azul' }, - ]; - expect(withoutSizes.some(v => v.size_code)).toBe(false); - }); - - it('step label changes based on hasSizes', () => { - const hasSizes = true; - expect(hasSizes ? 'Cor/Tam' : 'Cor').toBe('Cor/Tam'); - - const noSizes = false; - expect(noSizes ? 'Cor/Tam' : 'Cor').toBe('Cor'); - }); - - it('variant-specific pricing overrides product base price', () => { - const basePrice = 35.00; - const variant: ProductVariant = { code: 'v1', name: 'Preto', size_code: 'GG', sale_price: 42.50 }; - const effectivePrice = variant.sale_price ?? basePrice; - expect(effectivePrice).toBe(42.50); - }); - - it('null sale_price falls back to base price', () => { - const basePrice = 35.00; - const variant: ProductVariant = { code: 'v1', name: 'Preto', size_code: 'M', sale_price: null }; - const effectivePrice = variant.sale_price ?? basePrice; - expect(effectivePrice).toBe(35.00); - }); - - it('variant selection is required when variants exist', () => { - const variants: ProductVariant[] = [ - { code: 'v1', name: 'Preto' }, - { code: 'v2', name: 'Azul' }, - ]; - const selectedVariant: ProductVariant | null = null; - const hasVariants = variants.length > 0; - const needsVariantSelection = hasVariants && !selectedVariant; - expect(needsVariantSelection).toBe(true); - }); - - it('no variant selection needed when no variants', () => { - const variants: ProductVariant[] = []; - const selectedVariant: ProductVariant | null = null; - const hasVariants = variants.length > 0; - const needsVariantSelection = hasVariants && !selectedVariant; - expect(needsVariantSelection).toBe(false); - }); - - it('grouped view: color groups with size chips', () => { - const variants: ProductVariant[] = [ - { code: 'v1', name: 'Preto', hex: '#000', size_code: 'P' }, - { code: 'v2', name: 'Preto', hex: '#000', size_code: 'M' }, - { code: 'v3', name: 'Preto', hex: '#000', size_code: 'G' }, - { code: 'v4', name: 'Azul', hex: '#00F', size_code: 'P' }, - { code: 'v5', name: 'Azul', hex: '#00F', size_code: 'M' }, - ]; - - const hasSizes = variants.some(v => v.size_code); - expect(hasSizes).toBe(true); - - const grouped = new Map(); - for (const v of variants) { - const key = v.name || 'Padrão'; - if (!grouped.has(key)) grouped.set(key, []); - grouped.get(key)!.push(v); - } - - expect(grouped.size).toBe(2); - expect(grouped.get('Preto')).toHaveLength(3); - expect(grouped.get('Azul')).toHaveLength(2); - }); - - it('flat view: no sizes, simple color list', () => { - const variants: ProductVariant[] = [ - { code: 'v1', name: 'Preto', hex: '#000', stock: 100 }, - { code: 'v2', name: 'Azul', hex: '#00F', stock: 50 }, - { code: 'v3', name: 'Branco', hex: '#FFF', stock: 0 }, - ]; - const hasSizes = variants.some(v => v.size_code); - expect(hasSizes).toBe(false); - - // Sort by stock > 0 first, then alphabetically - const sorted = [...variants].sort((a, b) => { - const aStock = a.stock ?? 0; - const bStock = b.stock ?? 0; - if (aStock > 0 && bStock === 0) return -1; - if (aStock === 0 && bStock > 0) return 1; - return a.name.localeCompare(b.name); - }); - expect(sorted[0].name).toBe('Azul'); - expect(sorted[2].name).toBe('Branco'); // out of stock goes last - }); - - it('selection summary shows color + size', () => { - const variant: ProductVariant = { - code: 'v1', name: 'Preto', size_code: 'G', stock: 150, - }; - const summary = `Selecionado: ${variant.name}${variant.size_code ? ` — Tamanho: ${variant.size_code}` : ''}`; - expect(summary).toBe('Selecionado: Preto — Tamanho: G'); - }); - - it('selection summary shows only color when no size', () => { - const variant: ProductVariant = { - code: 'v1', name: 'Azul', - }; - const summary = `Selecionado: ${variant.name}${variant.size_code ? ` — Tamanho: ${variant.size_code}` : ''}`; - expect(summary).toBe('Selecionado: Azul'); - }); -}); - -// ============ 5.4 — Export/PDF with Size and Gender ============ -describe('Phase 5.4 — Export/PDF data enrichment', () => { - it('quote line includes size in export', () => { - const line = { - product: 'Camiseta', sku: 'CAM-001', color: 'Preto', - size: 'M', qty: 100, unitPrice: 35, total: 3500, - }; - const exportLine = `${line.product} | ${line.color} | ${line.size || '-'} | ${line.qty} | R$ ${line.unitPrice.toFixed(2)}`; - expect(exportLine).toContain('M'); - }); - - it('quote line without size uses dash', () => { - const line = { - product: 'Caneta', sku: 'CAN-001', color: 'Azul', - size: null as string | null, qty: 500, unitPrice: 5.50, - }; - const sizeDisplay = line.size || '-'; - expect(sizeDisplay).toBe('-'); - }); - - it('gender included in product specs', () => { - const product = { - name: 'Camiseta Polo', gender: 'masculino', - }; - const specs = [ - `Produto: ${product.name}`, - product.gender ? `Gênero: ${product.gender}` : null, - ].filter(Boolean); - expect(specs).toHaveLength(2); - expect(specs[1]).toBe('Gênero: masculino'); - }); - - it('no gender in specs when null', () => { - const product = { - name: 'Squeeze', gender: null as string | null, - }; - const specs = [ - `Produto: ${product.name}`, - product.gender ? `Gênero: ${product.gender}` : null, - ].filter(Boolean); - expect(specs).toHaveLength(1); - }); - - it('CSV export includes size column', () => { - const headers = ['Produto', 'SKU', 'Cor', 'Tamanho', 'Qtd', 'Preço Un.', 'Subtotal']; - expect(headers).toContain('Tamanho'); - - const row = ['Camiseta', 'CAM-001', 'Preto', 'M', '100', '35.00', '3500.00']; - expect(row[3]).toBe('M'); - }); -}); diff --git a/tests/e2e/products-catalog.test.ts b/tests/e2e/products-catalog.test.ts deleted file mode 100644 index c01332d9f..000000000 --- a/tests/e2e/products-catalog.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * E2E Tests — Products & Catalog Module - * Covers: Listing, Filtering, Sorting, Detail, Favorites, Comparison, Search - */ -import { describe, it, expect } from 'vitest'; - -// ============ Product Data Shape ============ -interface Product { - id: string; - nome: string; - codigo: string; - preco: number; - categoria: string; - fornecedor: string; - cor: string; - material: string; - estoque: number; - imagem_url: string | null; - ativo: boolean; -} - -const sampleProducts: Product[] = [ - { id: '1', nome: 'Caneta Esferográfica', codigo: 'CAN-001', preco: 5.50, categoria: 'Escritório', fornecedor: 'BIC', cor: 'Azul', material: 'Plástico', estoque: 5000, imagem_url: '/img/caneta.jpg', ativo: true }, - { id: '2', nome: 'Caderno Personalizado', codigo: 'CAD-001', preco: 25.00, categoria: 'Escritório', fornecedor: 'Tilibra', cor: 'Preto', material: 'Papel', estoque: 1200, imagem_url: '/img/caderno.jpg', ativo: true }, - { id: '3', nome: 'Mochila Executiva', codigo: 'MOC-001', preco: 89.90, categoria: 'Bags', fornecedor: 'Samsonite', cor: 'Preto', material: 'Nylon', estoque: 300, imagem_url: null, ativo: true }, - { id: '4', nome: 'Squeeze Térmico', codigo: 'SQZ-001', preco: 35.00, categoria: 'Bebidas', fornecedor: 'Stanley', cor: 'Branco', material: 'Aço Inox', estoque: 800, imagem_url: '/img/squeeze.jpg', ativo: true }, - { id: '5', nome: 'Chaveiro LED', codigo: 'CHA-001', preco: 8.90, categoria: 'Acessórios', fornecedor: 'GenBrand', cor: 'Vermelho', material: 'Metal', estoque: 10000, imagem_url: '/img/chaveiro.jpg', ativo: true }, - { id: '6', nome: 'Produto Inativo', codigo: 'INA-001', preco: 0, categoria: 'Outros', fornecedor: 'X', cor: 'Cinza', material: 'N/A', estoque: 0, imagem_url: null, ativo: false }, -]; - -// ============ Filtering ============ -function filterProducts(products: Product[], filters: { - search?: string; category?: string; supplier?: string; color?: string; - material?: string; minPrice?: number; maxPrice?: number; activeOnly?: boolean; -}): Product[] { - return products.filter(p => { - if (filters.activeOnly !== false && !p.ativo) return false; - if (filters.search && !p.nome.toLowerCase().includes(filters.search.toLowerCase()) && !p.codigo.toLowerCase().includes(filters.search.toLowerCase())) return false; - if (filters.category && p.categoria !== filters.category) return false; - if (filters.supplier && p.fornecedor !== filters.supplier) return false; - if (filters.color && p.cor !== filters.color) return false; - if (filters.material && p.material !== filters.material) return false; - if (filters.minPrice !== undefined && p.preco < filters.minPrice) return false; - if (filters.maxPrice !== undefined && p.preco > filters.maxPrice) return false; - return true; - }); -} - -describe('E2E Catalog — Product Filtering', () => { - it('active only by default', () => expect(filterProducts(sampleProducts, {}).length).toBe(5)); - it('includes inactive when specified', () => expect(filterProducts(sampleProducts, { activeOnly: false }).length).toBe(6)); - it('search by name', () => expect(filterProducts(sampleProducts, { search: 'caneta' })).toHaveLength(1)); - it('search by SKU', () => expect(filterProducts(sampleProducts, { search: 'CAD-001' })).toHaveLength(1)); - it('search case insensitive', () => expect(filterProducts(sampleProducts, { search: 'MOCHILA' })).toHaveLength(1)); - it('search no match', () => expect(filterProducts(sampleProducts, { search: 'xyz123' })).toHaveLength(0)); - it('filter by category', () => expect(filterProducts(sampleProducts, { category: 'Escritório' })).toHaveLength(2)); - it('filter by supplier', () => expect(filterProducts(sampleProducts, { supplier: 'BIC' })).toHaveLength(1)); - it('filter by color', () => expect(filterProducts(sampleProducts, { color: 'Preto' })).toHaveLength(2)); - it('filter by material', () => expect(filterProducts(sampleProducts, { material: 'Metal' })).toHaveLength(1)); - it('filter by price range', () => expect(filterProducts(sampleProducts, { minPrice: 10, maxPrice: 50 })).toHaveLength(2)); - it('filter by min price only', () => expect(filterProducts(sampleProducts, { minPrice: 50 })).toHaveLength(1)); - it('filter by max price only', () => expect(filterProducts(sampleProducts, { maxPrice: 10 })).toHaveLength(2)); - it('combined filters', () => expect(filterProducts(sampleProducts, { category: 'Escritório', color: 'Azul' })).toHaveLength(1)); - it('combined filters with no match', () => expect(filterProducts(sampleProducts, { category: 'Escritório', color: 'Vermelho' })).toHaveLength(0)); -}); - -// ============ Sorting ============ -type SortBy = 'name-asc' | 'name-desc' | 'price-asc' | 'price-desc' | 'stock-asc' | 'stock-desc'; - -function sortProducts(products: Product[], sortBy: SortBy): Product[] { - const sorted = [...products]; - switch (sortBy) { - case 'name-asc': return sorted.sort((a, b) => a.nome.localeCompare(b.nome)); - case 'name-desc': return sorted.sort((a, b) => b.nome.localeCompare(a.nome)); - case 'price-asc': return sorted.sort((a, b) => a.preco - b.preco); - case 'price-desc': return sorted.sort((a, b) => b.preco - a.preco); - case 'stock-asc': return sorted.sort((a, b) => a.estoque - b.estoque); - case 'stock-desc': return sorted.sort((a, b) => b.estoque - a.estoque); - default: return sorted; - } -} - -describe('E2E Catalog — Sorting', () => { - const active = sampleProducts.filter(p => p.ativo); - - it('sort by name asc', () => { - const sorted = sortProducts(active, 'name-asc'); - expect(sorted[0].nome).toBe('Caderno Personalizado'); - }); - it('sort by name desc', () => { - const sorted = sortProducts(active, 'name-desc'); - expect(sorted[0].nome).toBe('Squeeze Térmico'); - }); - it('sort by price asc', () => { - const sorted = sortProducts(active, 'price-asc'); - expect(sorted[0].preco).toBe(5.50); - }); - it('sort by price desc', () => { - const sorted = sortProducts(active, 'price-desc'); - expect(sorted[0].preco).toBe(89.90); - }); - it('sort by stock asc', () => { - const sorted = sortProducts(active, 'stock-asc'); - expect(sorted[0].estoque).toBe(300); - }); - it('sort by stock desc', () => { - const sorted = sortProducts(active, 'stock-desc'); - expect(sorted[0].estoque).toBe(10000); - }); - it('sort preserves length', () => { - expect(sortProducts(active, 'name-asc')).toHaveLength(active.length); - }); - it('sort does not mutate original', () => { - const original = [...active]; - sortProducts(active, 'price-desc'); - expect(active[0].id).toBe(original[0].id); - }); -}); - -// ============ Favorites ============ -describe('E2E Catalog — Favorites', () => { - let favorites: Set; - - beforeEach(() => { favorites = new Set(); }); - - it('starts empty', () => expect(favorites.size).toBe(0)); - it('add favorite', () => { favorites.add('1'); expect(favorites.has('1')).toBe(true); }); - it('remove favorite', () => { favorites.add('1'); favorites.delete('1'); expect(favorites.has('1')).toBe(false); }); - it('toggle on', () => { favorites.has('1') ? favorites.delete('1') : favorites.add('1'); expect(favorites.has('1')).toBe(true); }); - it('toggle off', () => { favorites.add('1'); favorites.has('1') ? favorites.delete('1') : favorites.add('1'); expect(favorites.has('1')).toBe(false); }); - it('multiple favorites', () => { favorites.add('1'); favorites.add('3'); favorites.add('5'); expect(favorites.size).toBe(3); }); - it('no duplicates', () => { favorites.add('1'); favorites.add('1'); expect(favorites.size).toBe(1); }); -}); - -// ============ Comparison ============ -describe('E2E Catalog — Comparison', () => { - const MAX_COMPARE = 4; - let compareList: string[]; - - beforeEach(() => { compareList = []; }); - - it('starts empty', () => expect(compareList).toHaveLength(0)); - it('add to compare', () => { compareList.push('1'); expect(compareList).toHaveLength(1); }); - it('remove from compare', () => { - compareList.push('1', '2'); - compareList = compareList.filter(id => id !== '1'); - expect(compareList).toHaveLength(1); - }); - it('max 4 items', () => { - compareList = ['1', '2', '3', '4']; - expect(compareList.length <= MAX_COMPARE).toBe(true); - }); - it('rejects 5th item', () => { - compareList = ['1', '2', '3', '4']; - if (compareList.length < MAX_COMPARE) compareList.push('5'); - expect(compareList).toHaveLength(4); - }); - it('is in compare check', () => { - compareList = ['1', '3']; - expect(compareList.includes('1')).toBe(true); - expect(compareList.includes('2')).toBe(false); - }); -}); - -// ============ Product Detail ============ -describe('E2E Catalog — Product Detail', () => { - const product = sampleProducts[0]; - - it('has name', () => expect(product.nome).toBeTruthy()); - it('has SKU', () => expect(product.codigo).toMatch(/^[A-Z]+-\d+$/)); - it('has price > 0', () => expect(product.preco).toBeGreaterThan(0)); - it('has category', () => expect(product.categoria).toBeTruthy()); - it('has supplier', () => expect(product.fornecedor).toBeTruthy()); - it('has stock count', () => expect(product.estoque).toBeGreaterThanOrEqual(0)); - it('image can be null', () => expect(sampleProducts[2].imagem_url).toBeNull()); - it('image can be URL', () => expect(product.imagem_url).toContain('/')); -}); - -// ============ View Modes ============ -describe('E2E Catalog — View Modes', () => { - const modes = ['grid', 'list', 'compact'] as const; - it('has 3 view modes', () => expect(modes).toHaveLength(3)); - modes.forEach(mode => { - it(`"${mode}" is valid`, () => expect(typeof mode).toBe('string')); - }); -}); - -// ============ Pagination ============ -describe('E2E Catalog — Pagination', () => { - const PAGE_SIZE = 20; - const totalItems = 87; - const totalPages = Math.ceil(totalItems / PAGE_SIZE); - - it('calculates total pages', () => expect(totalPages).toBe(5)); - it('page 1 offset is 0', () => expect((1 - 1) * PAGE_SIZE).toBe(0)); - it('page 3 offset is 40', () => expect((3 - 1) * PAGE_SIZE).toBe(40)); - it('last page items', () => expect(totalItems - (totalPages - 1) * PAGE_SIZE).toBe(7)); -}); - -import { beforeEach } from 'vitest'; diff --git a/tests/e2e/profile-user.test.ts b/tests/e2e/profile-user.test.ts deleted file mode 100644 index b0546bf29..000000000 --- a/tests/e2e/profile-user.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * E2E Tests — Profile & User Settings Module - * Covers: Profile form, avatar, password change, 2FA, preferences - */ -import { describe, it, expect } from 'vitest'; -import { z } from 'zod'; - -// ============ Profile Schema ============ -const profileSchema = z.object({ - full_name: z.string().min(2, 'Nome deve ter pelo menos 2 caracteres'), - email: z.string().email('Email inválido'), - phone: z.string().optional(), - department: z.string().optional(), -}); - -describe('E2E Profile — Form Validation', () => { - it('accepts valid profile', () => { - expect(profileSchema.safeParse({ full_name: 'João', email: 'j@t.com' }).success).toBe(true); - }); - it('rejects empty name', () => { - expect(profileSchema.safeParse({ full_name: '', email: 'j@t.com' }).success).toBe(false); - }); - it('rejects short name', () => { - expect(profileSchema.safeParse({ full_name: 'J', email: 'j@t.com' }).success).toBe(false); - }); - it('rejects invalid email', () => { - expect(profileSchema.safeParse({ full_name: 'João', email: 'invalid' }).success).toBe(false); - }); - it('phone is optional', () => { - expect(profileSchema.safeParse({ full_name: 'João', email: 'j@t.com', phone: '11999999999' }).success).toBe(true); - }); - it('department is optional', () => { - expect(profileSchema.safeParse({ full_name: 'João', email: 'j@t.com', department: 'Vendas' }).success).toBe(true); - }); -}); - -// ============ Avatar Upload ============ -describe('E2E Profile — Avatar', () => { - const validTypes = ['image/jpeg', 'image/png', 'image/webp']; - const maxSizeMB = 2; - - it('accepts JPEG', () => expect(validTypes).toContain('image/jpeg')); - it('accepts PNG', () => expect(validTypes).toContain('image/png')); - it('rejects SVG', () => expect(validTypes).not.toContain('image/svg+xml')); - it('max size is 2MB', () => expect(maxSizeMB).toBe(2)); - - it('validates file size', () => { - const fileSizeBytes = 1.5 * 1024 * 1024; // 1.5MB - expect(fileSizeBytes <= maxSizeMB * 1024 * 1024).toBe(true); - }); - it('rejects oversized file', () => { - const fileSizeBytes = 3 * 1024 * 1024; // 3MB - expect(fileSizeBytes <= maxSizeMB * 1024 * 1024).toBe(false); - }); -}); - -// ============ Password Change ============ -describe('E2E Profile — Password Change', () => { - const changePasswordSchema = z.object({ - currentPassword: z.string().min(1, 'Senha atual obrigatória'), - newPassword: z.string() - .min(8, 'Mínimo 8 caracteres') - .regex(/[A-Z]/, 'Precisa de maiúscula') - .regex(/[0-9]/, 'Precisa de número') - .regex(/[!@#$%^&*]/, 'Precisa de especial'), - confirmPassword: z.string(), - }).refine(d => d.newPassword === d.confirmPassword, { message: 'Senhas não conferem', path: ['confirmPassword'] }); - - it('accepts valid change', () => { - expect(changePasswordSchema.safeParse({ currentPassword: 'old123', newPassword: 'New1!pass', confirmPassword: 'New1!pass' }).success).toBe(true); - }); - it('rejects empty current', () => { - expect(changePasswordSchema.safeParse({ currentPassword: '', newPassword: 'New1!pass', confirmPassword: 'New1!pass' }).success).toBe(false); - }); - it('rejects weak new password', () => { - expect(changePasswordSchema.safeParse({ currentPassword: 'old', newPassword: 'weak', confirmPassword: 'weak' }).success).toBe(false); - }); - it('rejects mismatch', () => { - expect(changePasswordSchema.safeParse({ currentPassword: 'old', newPassword: 'New1!pass', confirmPassword: 'Different' }).success).toBe(false); - }); -}); - -// ============ User Preferences ============ -describe('E2E Profile — Preferences', () => { - const defaults = { - theme: 'dark', language: 'pt-BR', notifications: true, - emailNotifications: true, soundEffects: false, - gridColumns: 4, defaultView: 'grid', - }; - - it('default theme is dark', () => expect(defaults.theme).toBe('dark')); - it('language is pt-BR', () => expect(defaults.language).toBe('pt-BR')); - it('notifications enabled', () => expect(defaults.notifications).toBe(true)); - it('sound effects disabled', () => expect(defaults.soundEffects).toBe(false)); - it('grid columns is 4', () => expect(defaults.gridColumns).toBe(4)); - - it('toggle preference', () => { - const prefs = { ...defaults, notifications: !defaults.notifications }; - expect(prefs.notifications).toBe(false); - }); -}); diff --git a/tests/e2e/quote-resilience.test.ts b/tests/e2e/quote-resilience.test.ts deleted file mode 100644 index f93ecaa6e..000000000 --- a/tests/e2e/quote-resilience.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { test, expect } from "@playwright/test"; - -test.describe("Módulo Orçamentos - Resiliência e AutoSave", () => { - const STORAGE_KEY = "quote_builder_autosave"; - - test("Deve restaurar itens, campos e valores numéricos após refresh da página", async ({ page }) => { - await page.goto("/orcamentos/novo"); - - // 1. Preenche Empresa (dispara AutoSave) - const companyInput = page.locator('input[placeholder*="Buscar empresa"]'); - await companyInput.fill("Cliente Playwright"); - await page.keyboard.press("Enter"); - - // 2. Adiciona um produto com valor específico para validar cálculos - await page.click('button:has-text("Produto")'); - const firstProductAdd = page.locator('button:has-text("Adicionar")').first(); - await firstProductAdd.click(); - - // 3. Ajusta Markup (para garantir que valores numéricos complexos sejam salvos) - const markupInput = page.locator('input[name="markup"], [aria-label="Markup"]').first(); - if (await markupInput.isVisible()) { - await markupInput.fill("15"); - await page.keyboard.press("Tab"); - } - - // Aguarda o debounce do AutoSave (2000ms + margem) - await page.waitForTimeout(3000); - - // 4. Captura valores antes do refresh - const subtotalBefore = await page.locator('text=Subtotal:').locator('xpath=following-sibling::*').first().innerText(); - const totalBefore = await page.locator('text=Total:').locator('xpath=following-sibling::*').first().innerText(); - - // 5. Força Refresh - await page.reload(); - - // 6. Confirma restauração - await expect(page.locator("text=restaurado")).toBeVisible(); - - const itemsCount = page.locator("text=1 item(ns) adicionado(s)"); - await expect(itemsCount).toBeVisible(); - - // 7. Validação de Valores Numéricos Exatos - const subtotalAfter = await page.locator('text=Subtotal:').locator('xpath=following-sibling::*').first().innerText(); - const totalAfter = await page.locator('text=Total:').locator('xpath=following-sibling::*').first().innerText(); - - expect(subtotalAfter).toBe(subtotalBefore); - expect(totalAfter).toBe(totalBefore); - expect(totalAfter).not.toContain("R$ 0,00"); - }); - - test("Stepper deve refletir o progresso corretamente", async ({ page }) => { - await page.goto("/orcamentos/novo"); - - // Passo inicial: Cliente (Active) - const clientStep = page.locator('text=Cliente'); - await expect(clientStep).toHaveClass(/text-primary/); - - // Preenche cliente para avançar - await page.locator('input[placeholder*="Buscar empresa"]').fill("Teste Stepper"); - await page.keyboard.press("Enter"); - - // Passo 2: Itens deve se tornar ativo - const itemsStep = page.locator('text=Itens'); - await expect(itemsStep).toHaveClass(/text-primary/); - - // O conector entre Cliente e Itens deve estar colorido (bg-primary) - // Buscamos o div do conector após o passo Cliente - const connector = page.locator('[data-testid="quote-wizard"] .bg-primary').first(); - await expect(connector).toBeVisible(); - }); -}); diff --git a/tests/e2e/quotes-flow.test.ts b/tests/e2e/quotes-flow.test.ts deleted file mode 100644 index c4bdffc67..000000000 --- a/tests/e2e/quotes-flow.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -/** - * E2E Tests — Quotes Module - * Covers: CRUD, status flow, filtering, templates, approval, comments, history - */ -import { describe, it, expect, vi } from 'vitest'; - -// ============ Quote Status Machine ============ -const STATUSES = ['draft', 'pending', 'sent', 'approved', 'rejected', 'expired'] as const; -type QuoteStatus = typeof STATUSES[number]; - -const STATUS_LABELS: Record = { - draft: 'Rascunho', pending: 'Pendente', sent: 'Enviado', - approved: 'Aprovado', rejected: 'Rejeitado', expired: 'Expirado', -}; - -const VALID_TRANSITIONS: Record = { - draft: ['pending', 'sent'], - pending: ['sent', 'draft'], - sent: ['approved', 'rejected', 'expired'], - approved: [], - rejected: ['draft'], - expired: ['draft'], -}; - -function canTransition(from: QuoteStatus, to: QuoteStatus): boolean { - return VALID_TRANSITIONS[from]?.includes(to) ?? false; -} - -describe('E2E Quotes — Status Machine', () => { - it('has 6 statuses', () => expect(STATUSES).toHaveLength(6)); - - it('draft → sent is valid', () => expect(canTransition('draft', 'sent')).toBe(true)); - it('draft → pending is valid', () => expect(canTransition('draft', 'pending')).toBe(true)); - it('draft → approved is invalid', () => expect(canTransition('draft', 'approved')).toBe(false)); - it('sent → approved is valid', () => expect(canTransition('sent', 'approved')).toBe(true)); - it('sent → rejected is valid', () => expect(canTransition('sent', 'rejected')).toBe(true)); - it('sent → expired is valid', () => expect(canTransition('sent', 'expired')).toBe(true)); - it('approved → draft is invalid', () => expect(canTransition('approved', 'draft')).toBe(false)); - it('approved is terminal', () => expect(VALID_TRANSITIONS.approved).toHaveLength(0)); - it('rejected → draft is valid', () => expect(canTransition('rejected', 'draft')).toBe(true)); - it('expired → draft is valid', () => expect(canTransition('expired', 'draft')).toBe(true)); - - STATUSES.forEach(s => { - it(`status "${s}" has Portuguese label`, () => { - expect(STATUS_LABELS[s]).toBeTruthy(); - expect(typeof STATUS_LABELS[s]).toBe('string'); - }); - }); -}); - -// ============ Quote Calculations ============ -interface QuoteItem { - product_name: string; - quantity: number; - unit_price: number; -} - -function calcSubtotal(items: QuoteItem[]): number { - return items.reduce((sum, i) => sum + i.quantity * i.unit_price, 0); -} - -function calcDiscount(subtotal: number, percent: number, amount: number): number { - const percentDiscount = subtotal * (percent / 100); - return Math.min(percentDiscount + amount, subtotal); -} - -function calcTotal(subtotal: number, discountPercent: number, discountAmount: number, shipping: number): number { - const discount = calcDiscount(subtotal, discountPercent, discountAmount); - return Math.max(subtotal - discount + shipping, 0); -} - -describe('E2E Quotes — Calculations', () => { - const items: QuoteItem[] = [ - { product_name: 'Caneta', quantity: 100, unit_price: 5.50 }, - { product_name: 'Caderno', quantity: 50, unit_price: 25.00 }, - { product_name: 'Mochila', quantity: 10, unit_price: 89.90 }, - ]; - - it('calculates subtotal correctly', () => { - expect(calcSubtotal(items)).toBe(100 * 5.50 + 50 * 25 + 10 * 89.90); - }); - - it('subtotal with empty items is 0', () => { - expect(calcSubtotal([])).toBe(0); - }); - - it('subtotal with single item', () => { - expect(calcSubtotal([{ product_name: 'X', quantity: 1, unit_price: 10 }])).toBe(10); - }); - - it('10% discount on 1000', () => { - expect(calcDiscount(1000, 10, 0)).toBe(100); - }); - - it('discount amount + percentage combined', () => { - expect(calcDiscount(1000, 10, 50)).toBe(150); - }); - - it('discount cannot exceed subtotal', () => { - expect(calcDiscount(100, 100, 50)).toBe(100); - }); - - it('total = subtotal - discount + shipping', () => { - expect(calcTotal(1000, 10, 0, 50)).toBe(950); - }); - - it('total cannot be negative', () => { - expect(calcTotal(100, 100, 100, 0)).toBe(0); - }); - - it('total with zero discount and zero shipping', () => { - expect(calcTotal(500, 0, 0, 0)).toBe(500); - }); - - it('total with only shipping', () => { - expect(calcTotal(100, 0, 0, 30)).toBe(130); - }); -}); - -// ============ Quote Number Generation ============ -function generateQuoteNumber(prefix = 'ORC'): string { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const seq = String(Math.floor(Math.random() * 9999) + 1).padStart(4, '0'); - return `${prefix}-${year}${month}-${seq}`; -} - -describe('E2E Quotes — Number Generation', () => { - it('starts with ORC prefix', () => expect(generateQuoteNumber().startsWith('ORC-')).toBe(true)); - it('custom prefix works', () => expect(generateQuoteNumber('PROP').startsWith('PROP-')).toBe(true)); - it('has correct format length', () => expect(generateQuoteNumber().length).toBe(15)); - it('includes year', () => expect(generateQuoteNumber()).toContain(String(new Date().getFullYear()))); - it('generates unique numbers', () => { - const set = new Set(Array.from({ length: 100 }, () => generateQuoteNumber())); - expect(set.size).toBeGreaterThan(90); // At least 90% unique - }); -}); - -// ============ Quote Filtering ============ -interface Quote { - id: string; - status: QuoteStatus; - client_name: string | null; - total: number; - created_at: string; -} - -const sampleQuotes: Quote[] = [ - { id: '1', status: 'draft', client_name: 'Empresa Alpha', total: 1000, created_at: '2025-01-15' }, - { id: '2', status: 'sent', client_name: 'Beta Corp', total: 2500, created_at: '2025-02-10' }, - { id: '3', status: 'approved', client_name: 'Gamma LTDA', total: 500, created_at: '2025-03-01' }, - { id: '4', status: 'rejected', client_name: 'Delta SA', total: 8000, created_at: '2025-01-20' }, - { id: '5', status: 'draft', client_name: 'Epsilon ME', total: 150, created_at: '2025-03-15' }, - { id: '6', status: 'expired', client_name: null, total: 0, created_at: '2024-12-01' }, -]; - -function filterQuotes(quotes: Quote[], filters: { status?: QuoteStatus; search?: string; minTotal?: number }): Quote[] { - return quotes.filter(q => { - if (filters.status && q.status !== filters.status) return false; - if (filters.search && !(q.client_name?.toLowerCase().includes(filters.search.toLowerCase()))) return false; - if (filters.minTotal !== undefined && q.total < filters.minTotal) return false; - return true; - }); -} - -describe('E2E Quotes — Filtering', () => { - it('no filter returns all', () => expect(filterQuotes(sampleQuotes, {})).toHaveLength(6)); - it('filter by status=draft', () => expect(filterQuotes(sampleQuotes, { status: 'draft' })).toHaveLength(2)); - it('filter by status=approved', () => expect(filterQuotes(sampleQuotes, { status: 'approved' })).toHaveLength(1)); - it('filter by status=expired', () => expect(filterQuotes(sampleQuotes, { status: 'expired' })).toHaveLength(1)); - it('search by client name', () => expect(filterQuotes(sampleQuotes, { search: 'alpha' })).toHaveLength(1)); - it('search case insensitive', () => expect(filterQuotes(sampleQuotes, { search: 'BETA' })).toHaveLength(1)); - it('search with no match', () => expect(filterQuotes(sampleQuotes, { search: 'xyz' })).toHaveLength(0)); - it('search excludes null client_name', () => expect(filterQuotes(sampleQuotes, { search: 'a' })).not.toContainEqual(expect.objectContaining({ client_name: null }))); - it('filter by minTotal', () => expect(filterQuotes(sampleQuotes, { minTotal: 1000 })).toHaveLength(3)); - it('combined filters', () => expect(filterQuotes(sampleQuotes, { status: 'draft', minTotal: 500 })).toHaveLength(1)); -}); - -// ============ Quote Approval Token ============ -describe('E2E Quotes — Approval Token', () => { - const token = { - id: 'tok-1', quote_id: 'q-1', token: 'abc123def456', - seller_id: 'u-1', client_name: 'Cliente Teste', - client_email: 'cliente@test.com', status: 'active', - expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), - viewed_at: null, responded_at: null, response: null, response_notes: null, - }; - - it('token is active', () => expect(token.status).toBe('active')); - it('token has not expired', () => expect(new Date(token.expires_at) > new Date()).toBe(true)); - it('token has not been viewed', () => expect(token.viewed_at).toBeNull()); - it('token has no response', () => expect(token.response).toBeNull()); - it('client email is valid', () => expect(token.client_email).toMatch(/@/)); - - it('expired token detected', () => { - const expired = { ...token, expires_at: '2020-01-01T00:00:00Z' }; - expect(new Date(expired.expires_at) < new Date()).toBe(true); - }); - - it('approved response updates token', () => { - const approved = { ...token, status: 'responded', response: 'approved', responded_at: new Date().toISOString() }; - expect(approved.response).toBe('approved'); - expect(approved.responded_at).not.toBeNull(); - }); - - it('rejected response with notes', () => { - const rejected = { ...token, status: 'responded', response: 'rejected', response_notes: 'Preço muito alto' }; - expect(rejected.response).toBe('rejected'); - expect(rejected.response_notes).toBe('Preço muito alto'); - }); -}); - -// ============ Quote Templates ============ -describe('E2E Quotes — Templates', () => { - const template = { - id: 't-1', name: 'Template Padrão', description: 'Orçamento padrão para brindes', - seller_id: 'u-1', is_default: true, validity_days: 30, - payment_terms: 'À vista', delivery_time: '15 dias úteis', - items_data: [ - { product_name: 'Caneta', quantity: 100, unit_price: 5.50 }, - { product_name: 'Bloco', quantity: 50, unit_price: 12.00 }, - ], - }; - - it('has a name', () => expect(template.name).toBeTruthy()); - it('has items data', () => expect(template.items_data).toHaveLength(2)); - it('is default', () => expect(template.is_default).toBe(true)); - it('has payment terms', () => expect(template.payment_terms).toBe('À vista')); - it('validity is 30 days', () => expect(template.validity_days).toBe(30)); - it('delivery time specified', () => expect(template.delivery_time).toContain('dias')); -}); - -// ============ Quote History ============ -describe('E2E Quotes — History', () => { - const historyActions = ['created', 'status_changed', 'item_added', 'item_removed', 'discount_applied', 'sent', 'comment_added']; - - it('has all action types', () => expect(historyActions.length).toBeGreaterThanOrEqual(7)); - historyActions.forEach(action => { - it(`action "${action}" is a valid string`, () => expect(typeof action).toBe('string')); - }); -}); diff --git a/tests/e2e/seller-carts.test.ts b/tests/e2e/seller-carts.test.ts deleted file mode 100644 index 220c3ea74..000000000 --- a/tests/e2e/seller-carts.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -/** - * E2E Tests — Seller Carts Module (Comprehensive) - * Covers: Cart CRUD, items management, conversion to quote, templates, limits, and UI states. - */ -import { describe, it, expect, beforeEach } from 'vitest'; - -// ============================================ -// TYPES & MOCKS -// ============================================ - -interface SellerCartItem { - id: string; - cart_id: string; - product_id: string; - product_name: string; - product_price: number; - quantity: number; - color_name: string | null; - notes: string | null; - sort_order: number | null; - created_at: string; -} - -interface SellerCart { - id: string; - seller_id: string; - company_id: string; - company_name: string; - status: 'novo' | 'em_negociacao' | 'pronto_orcamento'; - items: SellerCartItem[]; - notes: string | null; - created_at: string; - updated_at: string; -} - -const CART_LIMIT = 3; - -// ============================================ -// TEST SUITE -// ============================================ - -describe('Módulo de Carrinhos (E2E Exhaustive)', () => { - let carts: SellerCart[] = []; - const sellerId = 'u-admin-1'; - - beforeEach(() => { - // Reset state for each test - carts = [ - { - id: 'cart-1', - seller_id: sellerId, - company_id: 'comp-alpha', - company_name: 'Alpha Corporate', - status: 'novo', - items: [ - { id: 'item-1', cart_id: 'cart-1', product_id: 'p-pen', product_name: 'Caneta Luxo', product_price: 15.00, quantity: 50, color_name: 'Azul', notes: null, sort_order: 0, created_at: new Date().toISOString() }, - { id: 'item-2', cart_id: 'cart-1', product_id: 'p-note', product_name: 'Caderno Moleskine', product_price: 45.00, quantity: 20, color_name: 'Preto', notes: 'Logo frontal', sort_order: 1, created_at: new Date().toISOString() } - ], - notes: 'Urgente para evento', - created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), // 5 days old - updated_at: new Date().toISOString() - } - ]; - }); - - describe('Fluxo Principal: CRUD e Gestão de Itens', () => { - it('deve adicionar um novo item ou incrementar quantidade se já existir', () => { - const cart = carts[0]; - const newItemId = 'p-pen'; // Already in cart - - const existing = cart.items.find(i => i.product_id === newItemId); - if (existing) { - existing.quantity += 10; - } - - expect(cart.items.find(i => i.product_id === 'p-pen')?.quantity).toBe(60); - }); - - it('deve remover um item e atualizar o total do carrinho', () => { - const cart = carts[0]; - cart.items = cart.items.filter(i => i.id !== 'item-1'); - expect(cart.items).toHaveLength(1); - const total = cart.items.reduce((sum, i) => sum + i.product_price * i.quantity, 0); - expect(total).toBe(45 * 20); - }); - - it('deve permitir editar notas individuais por item', () => { - const item = carts[0].items[0]; - item.notes = 'Gravação a laser'; - expect(carts[0].items[0].notes).toBe('Gravação a laser'); - }); - - it('deve reordenar itens preservando o sort_order', () => { - const items = carts[0].items; - // Inverte a ordem - const [first, second] = items; - carts[0].items = [second, first]; - carts[0].items.forEach((item, idx) => item.sort_order = idx); - - expect(carts[0].items[0].product_name).toBe('Caderno Moleskine'); - expect(carts[0].items[0].sort_order).toBe(0); - }); - }); - - describe('Regras de Negócio e Limites', () => { - it(`deve impedir a criação de mais de ${CART_LIMIT} carrinhos simultâneos`, () => { - // Adiciona mais 2 carrinhos para atingir o limite - carts.push({ id: 'cart-2', seller_id: sellerId, company_id: 'c2', company_name: 'Beta', status: 'novo', items: [], notes: null, created_at: '', updated_at: '' }); - carts.push({ id: 'cart-3', seller_id: sellerId, company_id: 'c3', company_name: 'Gamma', status: 'novo', items: [], notes: null, created_at: '', updated_at: '' }); - - const canCreate = carts.length < CART_LIMIT; - expect(canCreate).toBe(false); - - // Simulação da tentativa de criar o 4º - const tryCreate = () => { - if (carts.length >= CART_LIMIT) throw new Error('Limite atingido'); - }; - expect(tryCreate).toThrow('Limite atingido'); - }); - - it('deve permitir duplicar um carrinho com todos os seus itens', () => { - const source = carts[0]; - const duplicate: SellerCart = { - ...source, - id: 'cart-dup', - company_name: `${source.company_name} (Cópia)`, - items: source.items.map(i => ({ ...i, id: `item-dup-${i.id}`, cart_id: 'cart-dup' })) - }; - - expect(duplicate.items).toHaveLength(source.items.length); - expect(duplicate.items[0].product_name).toBe(source.items[0].product_name); - }); - }); - - describe('UI e Saúde do Carrinho', () => { - it('deve identificar carrinhos que precisam de follow-up (parados há > 3 dias)', () => { - const cart = carts[0]; - const ageInDays = (Date.now() - new Date(cart.created_at).getTime()) / (1000 * 60 * 60 * 24); - const needsFollowUp = ageInDays >= 3 && cart.items.length > 0; - expect(needsFollowUp).toBe(true); - }); - - it('deve exibir o dot de status correto (novo, em_negociacao, pronto)', () => { - const getStatusColor = (status: string) => { - if (status === 'novo') return 'bg-blue-500'; - if (status === 'em_negociacao') return 'bg-yellow-500'; - return 'bg-green-500'; - }; - - expect(getStatusColor(carts[0].status)).toBe('bg-blue-500'); - carts[0].status = 'em_negociacao'; - expect(getStatusColor(carts[0].status)).toBe('bg-yellow-500'); - }); - - it('deve calcular a saúde do carrinho com base na checklist', () => { - const cart = carts[0]; - const cartSubtotal = cart.items.reduce((sum, i) => sum + i.product_price * i.quantity, 0); - - const hasMinItems = cart.items.length >= 3; - const hasNotes = !!cart.notes && cart.notes.trim().length > 10; - const hasMinValue = cartSubtotal >= 500; - - const checks = [ - { id: "company", ok: !!cart.company_id }, - { id: "items", ok: hasMinItems }, - { id: "value", ok: hasMinValue }, - { id: "notes", ok: hasNotes } - ]; - - const okCount = checks.filter(c => c.ok).length; - expect(okCount).toBe(3); // Empresa, Valor (1650), Notas (Urgente...) OK. Itens (2) Falhou. - }); - }); - - describe('Inteligência Comercial: Bundle Suggestions', () => { - it('deve simular a adição de um item sugerido (cross-sell)', () => { - const cart = carts[0]; - const suggestion = { product_id: 'p-suggest', product_name: 'Embalagem Presente', product_price: 5.00 }; - - cart.items.push({ - id: 'item-suggested', - cart_id: cart.id, - product_id: suggestion.product_id, - product_name: suggestion.product_name, - product_price: suggestion.product_price, - quantity: 50, - color_name: null, - notes: null, - sort_order: 2, - created_at: new Date().toISOString() - }); - - expect(cart.items).toHaveLength(3); - expect(cart.items[2].product_id).toBe('p-suggest'); - }); - }); - - describe('Integração: Conversão para Orçamento', () => { - it('deve exportar itens no formato aceito pelo construtor de orçamentos', () => { - const cartItems = carts[0].items; - const quotePayload = cartItems.map(item => ({ - product_id: item.product_id, - quantity: item.quantity, - unit_price: item.product_price, - personalizations: item.notes ? [{ description: item.notes }] : [] - })); - - expect(quotePayload[1].personalizations[0].description).toBe('Logo frontal'); - expect(quotePayload[0].unit_price).toBe(15.00); - }); - }); -}); - - diff --git a/tests/e2e/tools-simulator.test.ts b/tests/e2e/tools-simulator.test.ts deleted file mode 100644 index c0087e61c..000000000 --- a/tests/e2e/tools-simulator.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * E2E Tests — Tools Module (Simulator, Mockup, Kit Builder, Price Search) - * Covers: Wizard steps, price calculation, mockup generation, kit assembly - */ -import { describe, it, expect } from 'vitest'; - -// ============ Simulator Wizard ============ -const WIZARD_STEPS = ['product', 'quantity', 'personalization', 'summary'] as const; - -describe('E2E Tools — Simulator Wizard', () => { - it('has 4 steps', () => expect(WIZARD_STEPS).toHaveLength(4)); - it('starts at product', () => expect(WIZARD_STEPS[0]).toBe('product')); - it('ends at summary', () => expect(WIZARD_STEPS[3]).toBe('summary')); - - it('step navigation forward', () => { - let current = 0; - current++; - expect(WIZARD_STEPS[current]).toBe('quantity'); - current++; - expect(WIZARD_STEPS[current]).toBe('personalization'); - }); - - it('step navigation backward', () => { - let current = 3; - current--; - expect(WIZARD_STEPS[current]).toBe('personalization'); - }); - - it('cannot go below step 0', () => { - let current = 0; - current = Math.max(0, current - 1); - expect(current).toBe(0); - }); - - it('cannot exceed max step', () => { - let current = 3; - current = Math.min(WIZARD_STEPS.length - 1, current + 1); - expect(current).toBe(3); - }); -}); - -// ============ Price Calculation ============ -interface PriceTier { minQty: number; maxQty: number; unitPrice: number; } - -const priceTiers: PriceTier[] = [ - { minQty: 1, maxQty: 49, unitPrice: 12.00 }, - { minQty: 50, maxQty: 99, unitPrice: 10.50 }, - { minQty: 100, maxQty: 249, unitPrice: 8.90 }, - { minQty: 250, maxQty: 499, unitPrice: 7.50 }, - { minQty: 500, maxQty: 999, unitPrice: 6.00 }, - { minQty: 1000, maxQty: Infinity, unitPrice: 5.00 }, -]; - -function getPriceForQuantity(qty: number, tiers: PriceTier[]): number { - const tier = tiers.find(t => qty >= t.minQty && qty <= t.maxQty); - return tier?.unitPrice ?? tiers[0].unitPrice; -} - -function calcSimulatorTotal(qty: number, tiers: PriceTier[], setupCost = 0): number { - return qty * getPriceForQuantity(qty, tiers) + setupCost; -} - -describe('E2E Tools — Price Tiers', () => { - it('1 unit = R$12.00', () => expect(getPriceForQuantity(1, priceTiers)).toBe(12.00)); - it('50 units = R$10.50', () => expect(getPriceForQuantity(50, priceTiers)).toBe(10.50)); - it('100 units = R$8.90', () => expect(getPriceForQuantity(100, priceTiers)).toBe(8.90)); - it('250 units = R$7.50', () => expect(getPriceForQuantity(250, priceTiers)).toBe(7.50)); - it('500 units = R$6.00', () => expect(getPriceForQuantity(500, priceTiers)).toBe(6.00)); - it('1000 units = R$5.00', () => expect(getPriceForQuantity(1000, priceTiers)).toBe(5.00)); - it('5000 units = R$5.00 (max tier)', () => expect(getPriceForQuantity(5000, priceTiers)).toBe(5.00)); - it('boundary: 49 units = R$12.00', () => expect(getPriceForQuantity(49, priceTiers)).toBe(12.00)); - it('boundary: 99 units = R$10.50', () => expect(getPriceForQuantity(99, priceTiers)).toBe(10.50)); - it('boundary: 999 units = R$6.00', () => expect(getPriceForQuantity(999, priceTiers)).toBe(6.00)); -}); - -describe('E2E Tools — Simulator Total', () => { - it('100 units no setup', () => expect(calcSimulatorTotal(100, priceTiers)).toBe(100 * 8.90)); - it('100 units with setup', () => expect(calcSimulatorTotal(100, priceTiers, 150)).toBe(100 * 8.90 + 150)); - it('1 unit', () => expect(calcSimulatorTotal(1, priceTiers)).toBe(12.00)); - it('1000 units', () => expect(calcSimulatorTotal(1000, priceTiers)).toBe(5000)); -}); - -// ============ Personalization Techniques ============ -const TECHNIQUES = [ - { id: 'silk', name: 'Serigrafia', maxColors: 6, setupPerColor: 80 }, - { id: 'laser', name: 'Gravação a Laser', maxColors: 1, setupPerColor: 0 }, - { id: 'sublim', name: 'Sublimação', maxColors: Infinity, setupPerColor: 0 }, - { id: 'border', name: 'Bordado', maxColors: 12, setupPerColor: 50 }, - { id: 'uv', name: 'Impressão UV', maxColors: Infinity, setupPerColor: 120 }, - { id: 'transfer', name: 'Transfer', maxColors: Infinity, setupPerColor: 60 }, -]; - -describe('E2E Tools — Personalization Techniques', () => { - it('has >= 5 techniques', () => expect(TECHNIQUES.length).toBeGreaterThanOrEqual(5)); - TECHNIQUES.forEach(t => { - it(`technique "${t.name}" has id`, () => expect(t.id).toBeTruthy()); - it(`technique "${t.name}" has maxColors > 0`, () => expect(t.maxColors).toBeGreaterThan(0)); - }); - it('laser has 1 max color', () => expect(TECHNIQUES.find(t => t.id === 'laser')?.maxColors).toBe(1)); - it('sublimação has infinite colors', () => expect(TECHNIQUES.find(t => t.id === 'sublim')?.maxColors).toBe(Infinity)); - - it('setup cost for serigrafia 3 colors', () => { - const silk = TECHNIQUES.find(t => t.id === 'silk')!; - expect(silk.setupPerColor * 3).toBe(240); - }); -}); - -// ============ Mockup Generator ============ -describe('E2E Tools — Mockup Generator', () => { - const mockupConfig = { - supportedFormats: ['png', 'jpg', 'svg'], - maxLogoSizeMB: 5, - canvasWidth: 800, canvasHeight: 600, - exportFormats: ['png', 'jpg'], - positions: ['front', 'back', 'left', 'right', 'pocket'], - }; - - it('supports common formats', () => expect(mockupConfig.supportedFormats).toContain('png')); - it('max logo size is reasonable', () => expect(mockupConfig.maxLogoSizeMB).toBeLessThanOrEqual(10)); - it('canvas has dimensions', () => { - expect(mockupConfig.canvasWidth).toBeGreaterThan(0); - expect(mockupConfig.canvasHeight).toBeGreaterThan(0); - }); - it('has export formats', () => expect(mockupConfig.exportFormats.length).toBeGreaterThan(0)); - it('has print positions', () => expect(mockupConfig.positions.length).toBeGreaterThanOrEqual(4)); -}); - -// ============ Kit Builder ============ -describe('E2E Tools — Kit Builder', () => { - const kit = { - items: [ - { productId: '1', name: 'Caneta', qty: 1, price: 5.50 }, - { productId: '2', name: 'Caderno', qty: 1, price: 25.00 }, - { productId: '3', name: 'Squeeze', qty: 1, price: 35.00 }, - ], - }; - - it('calculates kit total', () => { - const total = kit.items.reduce((s, i) => s + i.price * i.qty, 0); - expect(total).toBe(65.50); - }); - - it('add item to kit', () => { - const newKit = { ...kit, items: [...kit.items, { productId: '4', name: 'Mochila', qty: 1, price: 89.90 }] }; - expect(newKit.items).toHaveLength(4); - }); - - it('remove item from kit', () => { - const newKit = { ...kit, items: kit.items.filter(i => i.productId !== '1') }; - expect(newKit.items).toHaveLength(2); - }); - - it('kit total with quantities', () => { - const items = [{ productId: '1', name: 'X', qty: 100, price: 5 }]; - expect(items[0].qty * items[0].price).toBe(500); - }); -}); - -// ============ Advanced Price Search ============ -describe('E2E Tools — Price Search', () => { - const results = [ - { sku: 'CAN-001', name: 'Caneta', price: 5.50, supplier: 'BIC' }, - { sku: 'CAN-002', name: 'Caneta Premium', price: 12.00, supplier: 'Parker' }, - { sku: 'CAN-003', name: 'Caneta Metal', price: 18.50, supplier: 'Cross' }, - ]; - - it('search returns results', () => expect(results.length).toBeGreaterThan(0)); - it('results sorted by price asc', () => { - const sorted = [...results].sort((a, b) => a.price - b.price); - expect(sorted[0].price).toBe(5.50); - }); - it('filter by price range', () => { - const filtered = results.filter(r => r.price >= 10 && r.price <= 20); - expect(filtered).toHaveLength(2); - }); - it('supplier comparison', () => { - const suppliers = new Set(results.map(r => r.supplier)); - expect(suppliers.size).toBe(3); - }); -}); - -// ============ Stock Dashboard ============ -describe('E2E Tools — Stock Dashboard', () => { - const stockData = [ - { sku: 'CAN-001', available: 5000, reserved: 500, minimum: 100 }, - { sku: 'MOC-001', available: 50, reserved: 30, minimum: 100 }, - { sku: 'SQZ-001', available: 0, reserved: 0, minimum: 50 }, - ]; - - it('calculates net stock', () => { - const net = stockData[0].available - stockData[0].reserved; - expect(net).toBe(4500); - }); - - it('detects low stock', () => { - const lowStock = stockData.filter(s => (s.available - s.reserved) < s.minimum); - expect(lowStock).toHaveLength(2); - }); - - it('detects out of stock', () => { - const oos = stockData.filter(s => s.available === 0); - expect(oos).toHaveLength(1); - }); -}); diff --git a/tests/e2e/ui-components.test.tsx b/tests/e2e/ui-components.test.tsx deleted file mode 100644 index 319672ddd..000000000 --- a/tests/e2e/ui-components.test.tsx +++ /dev/null @@ -1,132 +0,0 @@ -/** - * E2E Tests — UI Components - * Covers: Button, Dialog, Toast, Input, Tabs, Select, Badge, Card - */ -import { describe, it, expect, vi } from 'vitest'; -import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { Button } from '@/components/ui/button'; - -describe('E2E UI — Button Variants', () => { - const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link', 'orange', 'premium', 'success'] as const; - - variants.forEach(variant => { - it(`renders "${variant}" variant`, () => { - render(); - expect(screen.getByText('Test')).toBeInTheDocument(); - }); - }); - - it('default variant renders', () => { - render(); - expect(screen.getByText('Default')).toBeInTheDocument(); - }); -}); - -describe('E2E UI — Button Sizes', () => { - const sizes = ['default', 'sm', 'lg', 'xl', 'icon'] as const; - - sizes.forEach(size => { - it(`renders "${size}" size`, () => { - render(); - expect(screen.getByText('S')).toBeInTheDocument(); - }); - }); -}); - -describe('E2E UI — Button Interactions', () => { - it('fires onClick', () => { - const onClick = vi.fn(); - render(); - fireEvent.click(screen.getByText('Click')); - expect(onClick).toHaveBeenCalledOnce(); - }); - - it('disabled does not fire onClick', () => { - const onClick = vi.fn(); - render(); - fireEvent.click(screen.getByText('Disabled')); - expect(onClick).not.toHaveBeenCalled(); - }); - - it('renders as child with asChild', () => { - render(); - expect(screen.getByText('Link').closest('a')).toHaveAttribute('href', '/test'); - }); - - it('has button role by default', () => { - render(); - expect(screen.getByRole('button')).toBeInTheDocument(); - }); - - it('supports type=submit', () => { - render(); - expect(screen.getByRole('button')).toHaveAttribute('type', 'submit'); - }); - - it('accepts className', () => { - render(); - expect(screen.getByText('Styled')).toHaveClass('custom-class'); - }); - - it('renders children', () => { - render(); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - }); -}); - -describe('E2E UI — Button Accessibility', () => { - it('has min touch target 44px', () => { - render(); - const btn = screen.getByRole('button'); - expect(btn.className).toContain('min-h-[44px]'); - }); - - it('has focus ring', () => { - render(); - const btn = screen.getByRole('button'); - expect(btn.className).toContain('focus-visible:ring'); - }); - - it('disabled has reduced opacity', () => { - render(); - const btn = screen.getByRole('button'); - expect(btn.className).toContain('disabled:opacity-50'); - }); -}); - -// ============ Status Badge Colors ============ -describe('E2E UI — Status Badges', () => { - const statusMap = { - draft: { label: 'Rascunho', color: 'gray' }, - sent: { label: 'Enviado', color: 'blue' }, - approved: { label: 'Aprovado', color: 'green' }, - rejected: { label: 'Rejeitado', color: 'red' }, - expired: { label: 'Expirado', color: 'orange' }, - }; - - Object.entries(statusMap).forEach(([status, { label, color }]) => { - it(`"${status}" maps to "${label}"`, () => expect(label).toBeTruthy()); - it(`"${status}" has color "${color}"`, () => expect(color).toBeTruthy()); - }); -}); - -// ============ Form Input Types ============ -describe('E2E UI — Input Types', () => { - const inputTypes = ['text', 'email', 'password', 'number', 'tel', 'search', 'url']; - - inputTypes.forEach(type => { - it(`supports type="${type}"`, () => { - render(); - expect(screen.getByTestId(`input-${type}`)).toHaveAttribute('type', type); - }); - }); -}); - -// ============ Loading States ============ -describe('E2E UI — Loading States', () => { - it('button shows loader when loading', () => { - render(); - expect(screen.getByText('Carregando...')).toBeInTheDocument(); - }); -}); diff --git a/vitest.config.ts b/vitest.config.ts index b05555257..a9e692591 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -24,11 +24,7 @@ export default defineConfig({ typecheck: { enabled: false, }, - // 'tests/e2e/**' excluído: contém testes Playwright (imports @playwright/test) - // que vitest tenta carregar e trava workers em deadlock. Playwright config - // usa testDir: './e2e' (não 'tests/e2e'), então esses 8 arquivos estavam - // órfãos. Ver fix(test): unblock vitest hang in CI. - exclude: ['node_modules', 'dist', '.idea', '.git', '.cache', 'tests/e2e/**'], + exclude: ['node_modules', 'dist', '.idea', '.git', '.cache'], // CI runners (GitHub Actions ubuntu-latest) têm 2 vCPU (4 vThreads). // Default thread pool causava timeout de 75min — mitigado com // maxThreads: 2 para evitar contenção.