From cac9a5b121d0db51ad63eb6b2765b6d2f2444ada Mon Sep 17 00:00:00 2001 From: Arturo Corro Date: Fri, 30 May 2025 01:07:33 -0600 Subject: [PATCH] Prepare component --- .editorconfig | 5 +- .gitattributes | 56 +- .github/CODEOWNERS | 25 + .github/CODE_OF_CONDUCT.md | 24 + .github/FUNDING.yml | 10 + .github/ISSUE_TEMPLATE/bug_report.md | 30 + .github/ISSUE_TEMPLATE/feature_request.md | 51 + .github/PULL_REQUEST_TEMPLATE.md | 55 + .github/SECURITY.md | 11 + .gitignore | 68 +- CHANGELOG.md | 56 +- CONTRIBUTING.md | 80 +- CONVENTIONS.md | 105 ++ Http/Controllers/ChatController.php | 19 - Http/Controllers/ContactFormController.php | 19 - Http/Controllers/ContactInfoController.php | 19 - Http/Controllers/FaqController.php | 19 - .../Controllers/GoogleAnalyticsController.php | 19 - Http/Controllers/ImagesController.php | 19 - Http/Controllers/LegalNoticesController.php | 19 - Http/Controllers/SitemapController.php | 19 - Http/Controllers/SocialMediaController.php | 19 - .../VuexyWebsiteAdminController.php | 19 - LICENSE | 73 +- LICENSE.es.md | 74 ++ Livewire/Faq/FaqIndex.php | 103 -- Livewire/Images/ImagesIndex.php | 104 -- Livewire/LegalNotices/LegalSettings.php | 108 -- Models/Faq.php | 33 - Models/SitemapUrl.php | 19 - .../VuexyWebsiteAdminServiceProvider.php | 97 -- README.en.md | 118 ++ README.md | 157 +-- SECURITY.md | 30 + composer.json | 14 +- config/koneko-website.php | 28 + config/vuexy_apis_catalog.php.back | 108 ++ config/vuexy_website_admin_menu copy.php | 339 +++++ config/vuexy_website_admin_menu.php | 218 ++++ database/data/rbac/permissions.json | 1154 +++++++++++++++++ database/data/rbac/roles.json | 280 ++++ .../website_agroform_contents.json | 192 +++ .../website-admin/website_agroform_menus.json | 66 + .../website_cleanfy_contents.json | 192 +++ .../website-admin/website_cleanfy_menus.json | 74 ++ .../data/website-admin/website_menus.json | 44 + .../website_realcity_contents.json | 192 +++ .../website-admin/website_realcity_menus.json | 92 ++ .../website-admin/website_seo_profiles.json | 145 +++ .../data/website-admin/website_sites.json | 220 ++++ ...1809_create_website_seo_profiles_table.php | 77 ++ ...2_29_081811_create_website_sites_table.php | 64 + ...2_29_081815_create_website_menus_table.php | 48 + ...081816_create_website_menu_items_table.php | 75 ++ ...9_082518_create_website_contents_table.php | 84 ++ ...20_create_website_content_blocks_table.php | 48 + ..._create_website_content_versions_table.php | 42 + ...9_090535_create_sitemap_profiles_table.php | 44 + ...2_29_090537_create_sitemap_rules_table.php | 32 + ...2_29_090539_create_sitemap_urls_table.php} | 19 +- ...90548_create_sitemap_index_files_table.php | 32 + ...30_082012_create_faq_categories_table.php} | 16 +- ...> 2024_12_30_082017_create_faqs_table.php} | 12 +- ...30_082125_create_blog_categories_table.php | 60 + ...24_12_30_082126_create_blog_tags_table.php | 48 + ...2_30_082127_create_blog_articles_table.php | 53 + ...0_082128_create_blog_article_tag_table.php | 39 + ...2_30_082129_create_blog_comments_table.php | 47 + .../create_sitemap_configurations_table.php | 23 - database/rbac copy/permissions.json | 70 + database/rbac copy/roles.json | 177 +++ resources/js/chat-settings-card.js | 6 +- resources/js/contact-form-settings-card.js | 12 +- resources/js/contact-info-settings-card.js | 14 +- .../js/google-analytics-settings-card.js | 8 +- resources/js/website-settings-card.js | 8 +- .../google-analytics/index.blade.php | 4 +- .../google-search-console/index.blade.php | 11 + .../analytics/google-tags/index.blade.php | 11 + .../analytics/pixel-meta/index.blade.php | 11 + resources/views/blog/article/index.blade.php | 31 + resources/views/blog/category/index.blade.php | 31 + resources/views/blog/comment/index.blade.php | 31 + resources/views/blog/tag/index.blade.php | 31 + .../comunication/messenger/index.blade.php | 11 + .../comunication/tawk-to/index.blade.php | 11 + .../twitter}/index.blade.php | 10 +- .../whatsapp}/index.blade.php | 4 +- .../form}/index.blade.php | 4 +- .../info}/index.blade.php | 6 +- .../views/{ => content}/faq/index.blade.php | 0 .../gallery}/index.blade.php | 2 +- .../legal}/index.blade.php | 2 +- .../legal}/legal-index.blade.php | 6 +- resources/views/layouts/base/header.blade.php | 72 + .../google-analytics-card.blade.php} | 0 .../google-search-console-card.blade.php | 34 + .../google-tags/google-tags-card.blade.php | 34 + .../pixel-meta/pixel-meta-card.blade.php | 34 + .../{faq => blog/article}/index.blade.php | 0 .../livewire/blog/category/index.blade.php | 7 + .../livewire/blog/comment/index.blade.php | 7 + .../views/livewire/blog/tag/index.blade.php | 7 + .../messenger/messenger-card.blade.php} | 0 .../tawk-to/tawk-to-card.blade.php | 58 + .../twitter/twitter-card.blade.php | 34 + .../whatsapp/whatsapp-card.blade.php | 58 + .../form/contact-form-card.blade.php} | 2 +- .../info/contact-info-card.blade.php} | 2 +- .../info/location-card.blade.php} | 2 +- .../livewire/content/faq/index.blade.php | 7 + .../gallery}/index.blade.php | 0 .../legal-notices/index.blade.php | 0 .../canonical}/index.blade.php | 0 .../views/livewire/seo/jsonld/index.blade.php | 22 + .../seo/manifest/manifest-card.blade.php | 22 + .../livewire/seo/robots/robot-card.blade.php | 22 + .../seo/sitemap/UrlOffcanvas-form.blade.php | 39 + .../livewire/seo/sitemap/index.blade.php | 22 + .../livewire/seo/social-cards/index.blade.php | 22 + .../general/___template-card.blade.php} | 0 .../general/logo-on-dark-bg-card.blade.php} | 2 +- .../general/logo-on-light-bg-card.blade.php} | 2 +- .../website-description-card.blade.php} | 3 +- .../general/website-favicon-card.blade.php} | 2 +- .../settings/indexing/indexing-card.blade.php | 4 + .../social/social-card.blade.php} | 0 .../google/google-translate-card.blade.php | 58 + resources/views/seo/canonical/index.blade.php | 7 + resources/views/seo/jsonld/index.blade.php | 7 + resources/views/seo/manifest/index.blade.php | 7 + resources/views/seo/robots/index.blade.php | 7 + .../sitemap}/index.blade.php | 2 +- .../views/seo/social-cards/index.blade.php | 7 + .../general}/index.blade.php | 8 +- .../views/settings/indexing/index.blade.php | 11 + .../views/settings/social/index.blade.php | 27 + .../views/translate/google/index.blade.php | 11 + .../website/blocks/call-to-action.blade.php | 0 .../views/website/blocks/features.blade.php | 0 resources/views/website/blocks/hero.blade.php | 0 .../views/website/templates/default.blade.php | 540 ++++++++ routes/admin.php | 59 - routes/koneko_website_admin.php | 64 + routes/koneko_website_blog.php | 44 + routes/koneko_website_cms.php | 8 + routes/koneko_website_sites.php | 14 + .../Bootstrap/Context/SiteContext.php | 72 + .../Cache/Builders/BlogLayoutVarsBuilder.php | 17 + .../Builders/EcommerceLayoutVarsBuilder.php | 17 + .../Builders/LandingLayoutVarsBuilder.php | 17 + .../Builders/WebsiteLayoutVarsBuilder.php | 17 + .../Cache/Builders/WebsiteSeoVarsBuilder.php | 17 + .../Builders/WebsiteSocialVarsBuilder.php | 17 + src/Application/Enums/WebsiteContentType.php | 18 + .../WebsiteMenuItem/WebsiteMenuItemTarget.php | 14 + .../WebsiteMenuItem/WebsiteMenuItemType.php | 17 + .../WebsiteSeoProfileType.php | 14 + .../Http/Controllers/AnalyticsController.php | 35 + .../Controllers/BlogArticleController.php | 21 + .../Controllers/BlogCategoryController.php | 21 + .../Controllers/BlogCommentController.php | 21 + .../Http/Controllers/BlogTagController.php | 21 + .../Controllers/ComunicationController.php | 36 + .../Http/Controllers/ContactController.php | 25 + .../Http/Controllers/ContentController.php | 37 + .../Http/Controllers/SeoController.php | 45 + .../Http/Controllers/SettingsController.php | 30 + .../Http/Controllers/TranstaleController.php | 20 + .../Controllers/WebsitePageController.php | 49 + .../WebsiteContentMiddleware copy.php | 65 + .../Middleware/WebsiteContentMiddleware.php | 67 + .../Middleware/WebsiteContextMiddleware.php | 94 ++ .../Middleware/WebsiteTemplateMiddleware.php | 33 + src/Application/LocalModule.php | 11 + .../VuexyAdminImageHandlerService.php | 198 +++ .../Template/___WebsiteSettingsService.php | 6 +- .../Template/___WebsiteTemplateService.php | 13 +- .../GoogleAnalytics/GoogleAnalyticsCard.php | 65 + .../GoogleSearchConsoleCard.php | 65 + .../Analytics/GoogleTags/GoogleTagsCard.php | 65 + .../Analytics/PixelMeta/PixelMetaCard.php | 65 + .../Blog/Article/BlogArticleOffcanvasForm.php | 219 ++++ .../Blog/Article/BlogArticlesTable.php | 27 + .../Blog/Category/BlogCategoriesTable.php | 27 + .../Category/BlogCategoryOffcanvasForm.php | 219 ++++ .../Blog/Comment/BlogCommentOffcanvasForm.php | 219 ++++ .../Blog/Comment/BlogCommentsTable.php | 27 + .../Blog/Tag/BlogTagOffcanvasForm.php | 218 ++++ .../UI/Livewire/Blog/Tag/BlogTagsTable.php | 27 + .../Comunication/Messenger/MessengerCard.php | 69 + .../Comunication/TawkTo/TawkToCard.php | 12 +- .../Comunication/Twitter/TwitterCard.php | 12 +- .../Comunication/Whatsapp/WhatsappCard.php | 69 + .../Livewire/Contact/Form/ContactFormCard.php | 14 +- .../Livewire/Contact/Info/ContactInfoCard.php | 14 +- .../Contact/Info/ContactLocationCard.php | 14 +- .../UI/Livewire/Content/Faq/FaqIndex.php | 27 + .../Content}/Faq/FaqOffcanvasForm.php | 14 +- .../Livewire/Content/Gallery/GalleryIndex.php | 15 + .../UI/Livewire/Content/Legal/LegalIndex.php | 13 +- .../Content/Legal/LegalOffCanvasForm.php | 18 +- .../Livewire/Seo/Canonical/CanonicalIndex.php | 40 + .../UI/Livewire/Seo/Jsonld/JsonldIndex.php | 41 + .../UI/Livewire/Seo/Manifest/ManifestCard.php | 40 + .../UI/Livewire/Seo/Robots/RobotsCard.php | 41 + .../UI/Livewire/Seo/Sitemap/SitemapIndex.php | 8 +- .../Seo/Sitemap}/SitemapUrlOffcanvasForm.php | 16 +- .../Seo/SocialCards/SocialCardsIndex.php | 40 + .../Settings/General/LogoOnDarkBgCard.php | 22 +- .../Settings/General/LogoOnLightBgCard.php | 22 +- .../General/WebsiteDescriptionCard.php | 32 +- .../Settings/General/WebsiteFaviconCard.php | 20 +- .../Settings/Indexing/IndexingCard.php | 101 ++ .../Livewire/Settings/Social/SocialCard.php | 12 +- .../Translate/Google/GoogleTanslateCard.php | 69 + .../Blog/ArticlesTableConfigBuilder.php | 175 +++ .../Blog/CategoriesTableConfigBuilder.php | 175 +++ .../Blog/CommentsTableConfigBuilder.php | 175 +++ .../Blog/TagsTableConfigBuilder.php | 175 +++ .../Faq/FaqTableConfigBuilder.php | 176 +++ .../Console}/Commands/SitemapGenerate.php | 4 +- .../Commands/WebsiteCacheHelperCommand.php | 65 + .../Commands/WebsiteContentHelperCommand.php | 114 ++ .../Commands/WebsiteMenuHelperCommand.php | 134 ++ .../Commands/WebsiteSeoHelperCommand.php | 73 ++ src/Database/Seeders/WebsiteContentSeeder.php | 40 + .../Seeders/WebsiteMenuItemSeeder.php | 41 + src/Database/Seeders/WebsiteMenuSeeder.php | 21 + .../Seeders/WebsiteSeoProfileSeeder.php | 34 + src/Database/Seeders/WebsiteSiteSeeder.php | 21 + src/Models/BlogArticle.php | 92 ++ src/Models/BlogCategory.php | 64 + src/Models/BlogComment.php | 55 + src/Models/BlogTag.php | 48 + src/Models/Faq.php | 61 + {Models => src/Models}/FaqCategory.php | 18 +- src/Models/SitemapIndexFile.php | 42 + src/Models/SitemapProfile.php | 63 + src/Models/SitemapRule.php | 36 + src/Models/SitemapUrl.php | 46 + src/Models/WebsiteContent.php | 194 +++ src/Models/WebsiteContentBlock.php | 158 +++ src/Models/WebsiteContentVersion.php | 64 + src/Models/WebsiteMenu.php | 72 + src/Models/WebsiteMenuItem.php | 121 ++ src/Models/WebsiteSeoProfile.php | 141 ++ src/Models/WebsiteSite.php | 87 ++ .../VuexyWebsiteAdminServiceProvider.php | 18 + src/Traits/Context/HasSiteContext.php | 60 + src/Website/Cache/RenderCacheInvalidator.php | 17 + .../Cache/WebsiteRenderCacheService.php | 74 ++ src/Website/Enums/WebsiteSiteStatus.php | 10 + src/Website/Menu/WebsiteMenuRenderer.php | 165 +++ src/Website/Traits/Cache/HasRenderCache.php | 28 + .../WebsiteBreadcrumbsBuilderService.php | 107 ++ .../___VuexyQuicklinksBuilderService.php | 92 ++ .../Header/___VuexySearchBuilderService.php | 72 + src/Website/UX/Menu/___VuexyMenuRegistry.php | 242 ++++ .../UX/Menu/___WebsiteMenuBuilderService.php | 380 ++++++ .../UX/Template/WebsiteVarsBuilderService.php | 115 ++ .../UX/Template/___VuexyInterfaceService.php | 237 ++++ src/koneko-vuexy.module.php | 187 +++ 263 files changed, 14456 insertions(+), 1072 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/SECURITY.md create mode 100644 CONVENTIONS.md delete mode 100644 Http/Controllers/ChatController.php delete mode 100644 Http/Controllers/ContactFormController.php delete mode 100644 Http/Controllers/ContactInfoController.php delete mode 100644 Http/Controllers/FaqController.php delete mode 100644 Http/Controllers/GoogleAnalyticsController.php delete mode 100644 Http/Controllers/ImagesController.php delete mode 100644 Http/Controllers/LegalNoticesController.php delete mode 100644 Http/Controllers/SitemapController.php delete mode 100644 Http/Controllers/SocialMediaController.php delete mode 100644 Http/Controllers/VuexyWebsiteAdminController.php create mode 100644 LICENSE.es.md delete mode 100644 Livewire/Faq/FaqIndex.php delete mode 100644 Livewire/Images/ImagesIndex.php delete mode 100644 Livewire/LegalNotices/LegalSettings.php delete mode 100644 Models/Faq.php delete mode 100644 Models/SitemapUrl.php delete mode 100644 Providers/VuexyWebsiteAdminServiceProvider.php create mode 100644 README.en.md create mode 100644 SECURITY.md create mode 100644 config/koneko-website.php create mode 100644 config/vuexy_apis_catalog.php.back create mode 100644 config/vuexy_website_admin_menu copy.php create mode 100644 config/vuexy_website_admin_menu.php create mode 100644 database/data/rbac/permissions.json create mode 100644 database/data/rbac/roles.json create mode 100644 database/data/website-admin/website_agroform_contents.json create mode 100644 database/data/website-admin/website_agroform_menus.json create mode 100644 database/data/website-admin/website_cleanfy_contents.json create mode 100644 database/data/website-admin/website_cleanfy_menus.json create mode 100644 database/data/website-admin/website_menus.json create mode 100644 database/data/website-admin/website_realcity_contents.json create mode 100644 database/data/website-admin/website_realcity_menus.json create mode 100644 database/data/website-admin/website_seo_profiles.json create mode 100644 database/data/website-admin/website_sites.json create mode 100644 database/migrations/2024_12_29_081809_create_website_seo_profiles_table.php create mode 100644 database/migrations/2024_12_29_081811_create_website_sites_table.php create mode 100644 database/migrations/2024_12_29_081815_create_website_menus_table.php create mode 100644 database/migrations/2024_12_29_081816_create_website_menu_items_table.php create mode 100644 database/migrations/2024_12_29_082518_create_website_contents_table.php create mode 100644 database/migrations/2024_12_29_082520_create_website_content_blocks_table.php create mode 100644 database/migrations/2024_12_29_082521_create_website_content_versions_table.php create mode 100644 database/migrations/2024_12_29_090535_create_sitemap_profiles_table.php create mode 100644 database/migrations/2024_12_29_090537_create_sitemap_rules_table.php rename database/migrations/{create_sitemap_urls_table.php => 2024_12_29_090539_create_sitemap_urls_table.php} (53%) create mode 100644 database/migrations/2024_12_29_090548_create_sitemap_index_files_table.php rename database/migrations/{2024_12_29_081812_create_faq_categories_table.php => 2024_12_30_082012_create_faq_categories_table.php} (50%) rename database/migrations/{2024_12_29_081815_create_faqs_table.php => 2024_12_30_082017_create_faqs_table.php} (63%) create mode 100644 database/migrations/2024_12_30_082125_create_blog_categories_table.php create mode 100644 database/migrations/2024_12_30_082126_create_blog_tags_table.php create mode 100644 database/migrations/2024_12_30_082127_create_blog_articles_table.php create mode 100644 database/migrations/2024_12_30_082128_create_blog_article_tag_table.php create mode 100644 database/migrations/2024_12_30_082129_create_blog_comments_table.php delete mode 100644 database/migrations/create_sitemap_configurations_table.php create mode 100644 database/rbac copy/permissions.json create mode 100644 database/rbac copy/roles.json rename resources/views/{ => analytics}/google-analytics/index.blade.php (88%) create mode 100644 resources/views/analytics/google-search-console/index.blade.php create mode 100644 resources/views/analytics/google-tags/index.blade.php create mode 100644 resources/views/analytics/pixel-meta/index.blade.php create mode 100644 resources/views/blog/article/index.blade.php create mode 100644 resources/views/blog/category/index.blade.php create mode 100644 resources/views/blog/comment/index.blade.php create mode 100644 resources/views/blog/tag/index.blade.php create mode 100644 resources/views/comunication/messenger/index.blade.php create mode 100644 resources/views/comunication/tawk-to/index.blade.php rename resources/views/{social-media => comunication/twitter}/index.blade.php (77%) rename resources/views/{chat => comunication/whatsapp}/index.blade.php (89%) rename resources/views/{contact-form => contact/form}/index.blade.php (89%) rename resources/views/{contact-info => contact/info}/index.blade.php (84%) rename resources/views/{ => content}/faq/index.blade.php (100%) rename resources/views/{images => content/gallery}/index.blade.php (82%) rename resources/views/{legal-notices => content/legal}/index.blade.php (67%) rename resources/views/{legal-notices => content/legal}/legal-index.blade.php (83%) create mode 100644 resources/views/layouts/base/header.blade.php rename resources/views/livewire/{vuexy/analytics-settings.blade.php => analytics/google-analytics/google-analytics-card.blade.php} (100%) create mode 100644 resources/views/livewire/analytics/google-search-console/google-search-console-card.blade.php create mode 100644 resources/views/livewire/analytics/google-tags/google-tags-card.blade.php create mode 100644 resources/views/livewire/analytics/pixel-meta/pixel-meta-card.blade.php rename resources/views/livewire/{faq => blog/article}/index.blade.php (100%) create mode 100644 resources/views/livewire/blog/category/index.blade.php create mode 100644 resources/views/livewire/blog/comment/index.blade.php create mode 100644 resources/views/livewire/blog/tag/index.blade.php rename resources/views/livewire/{vuexy/chat-settings.blade.php => comunication/messenger/messenger-card.blade.php} (100%) create mode 100644 resources/views/livewire/comunication/tawk-to/tawk-to-card.blade.php create mode 100644 resources/views/livewire/comunication/twitter/twitter-card.blade.php create mode 100644 resources/views/livewire/comunication/whatsapp/whatsapp-card.blade.php rename resources/views/livewire/{vuexy/contact-form-settings.blade.php => contact/form/contact-form-card.blade.php} (93%) rename resources/views/livewire/{vuexy/contact-info-settings.blade.php => contact/info/contact-info-card.blade.php} (94%) rename resources/views/livewire/{vuexy/location-settings.blade.php => contact/info/location-card.blade.php} (92%) create mode 100644 resources/views/livewire/content/faq/index.blade.php rename resources/views/livewire/{images => content/gallery}/index.blade.php (100%) rename resources/views/livewire/{ => content}/legal-notices/index.blade.php (100%) rename resources/views/livewire/{sitemap-manager => seo/canonical}/index.blade.php (100%) create mode 100644 resources/views/livewire/seo/jsonld/index.blade.php create mode 100644 resources/views/livewire/seo/manifest/manifest-card.blade.php create mode 100644 resources/views/livewire/seo/robots/robot-card.blade.php create mode 100644 resources/views/livewire/seo/sitemap/UrlOffcanvas-form.blade.php create mode 100644 resources/views/livewire/seo/sitemap/index.blade.php create mode 100644 resources/views/livewire/seo/social-cards/index.blade.php rename resources/views/livewire/{vuexy/template-settings.blade.php => settings/general/___template-card.blade.php} (100%) rename resources/views/livewire/{vuexy/logo-on-dark-bg-settings.blade.php => settings/general/logo-on-dark-bg-card.blade.php} (96%) rename resources/views/livewire/{vuexy/logo-on-light-bg-settings.blade.php => settings/general/logo-on-light-bg-card.blade.php} (96%) rename resources/views/livewire/{vuexy/website-description-settings.blade.php => settings/general/website-description-card.blade.php} (85%) rename resources/views/livewire/{vuexy/website-favicon-settings.blade.php => settings/general/website-favicon-card.blade.php} (98%) create mode 100644 resources/views/livewire/settings/indexing/indexing-card.blade.php rename resources/views/livewire/{vuexy/social-media-settings.blade.php => settings/social/social-card.blade.php} (100%) create mode 100644 resources/views/livewire/translate/google/google-translate-card.blade.php create mode 100644 resources/views/seo/canonical/index.blade.php create mode 100644 resources/views/seo/jsonld/index.blade.php create mode 100644 resources/views/seo/manifest/index.blade.php create mode 100644 resources/views/seo/robots/index.blade.php rename resources/views/{sitemap-manager => seo/sitemap}/index.blade.php (67%) create mode 100644 resources/views/seo/social-cards/index.blade.php rename resources/views/{general-settings => settings/general}/index.blade.php (66%) create mode 100644 resources/views/settings/indexing/index.blade.php create mode 100644 resources/views/settings/social/index.blade.php create mode 100644 resources/views/translate/google/index.blade.php rename Models/SitemapConfiguration.php => resources/views/website/blocks/call-to-action.blade.php (100%) create mode 100644 resources/views/website/blocks/features.blade.php create mode 100644 resources/views/website/blocks/hero.blade.php create mode 100644 resources/views/website/templates/default.blade.php delete mode 100644 routes/admin.php create mode 100644 routes/koneko_website_admin.php create mode 100644 routes/koneko_website_blog.php create mode 100644 routes/koneko_website_cms.php create mode 100644 routes/koneko_website_sites.php create mode 100644 src/Application/Bootstrap/Context/SiteContext.php create mode 100644 src/Application/Cache/Builders/BlogLayoutVarsBuilder.php create mode 100644 src/Application/Cache/Builders/EcommerceLayoutVarsBuilder.php create mode 100644 src/Application/Cache/Builders/LandingLayoutVarsBuilder.php create mode 100644 src/Application/Cache/Builders/WebsiteLayoutVarsBuilder.php create mode 100644 src/Application/Cache/Builders/WebsiteSeoVarsBuilder.php create mode 100644 src/Application/Cache/Builders/WebsiteSocialVarsBuilder.php create mode 100644 src/Application/Enums/WebsiteContentType.php create mode 100644 src/Application/Enums/WebsiteMenuItem/WebsiteMenuItemTarget.php create mode 100644 src/Application/Enums/WebsiteMenuItem/WebsiteMenuItemType.php create mode 100644 src/Application/Enums/WebsiteSeoProfile/WebsiteSeoProfileType.php create mode 100644 src/Application/Http/Controllers/AnalyticsController.php create mode 100644 src/Application/Http/Controllers/BlogArticleController.php create mode 100644 src/Application/Http/Controllers/BlogCategoryController.php create mode 100644 src/Application/Http/Controllers/BlogCommentController.php create mode 100644 src/Application/Http/Controllers/BlogTagController.php create mode 100644 src/Application/Http/Controllers/ComunicationController.php create mode 100644 src/Application/Http/Controllers/ContactController.php create mode 100644 src/Application/Http/Controllers/ContentController.php create mode 100644 src/Application/Http/Controllers/SeoController.php create mode 100644 src/Application/Http/Controllers/SettingsController.php create mode 100644 src/Application/Http/Controllers/TranstaleController.php create mode 100644 src/Application/Http/Controllers/WebsitePageController.php create mode 100644 src/Application/Http/Middleware/WebsiteContentMiddleware copy.php create mode 100644 src/Application/Http/Middleware/WebsiteContentMiddleware.php create mode 100644 src/Application/Http/Middleware/WebsiteContextMiddleware.php create mode 100644 src/Application/Http/Middleware/WebsiteTemplateMiddleware.php create mode 100644 src/Application/LocalModule.php create mode 100644 src/Application/Template/VuexyAdminImageHandlerService.php rename Services/WebsiteSettingsService.php => src/Application/Template/___WebsiteSettingsService.php (98%) rename Services/WebsiteTemplateService.php => src/Application/Template/___WebsiteTemplateService.php (97%) create mode 100644 src/Application/UI/Livewire/Analytics/GoogleAnalytics/GoogleAnalyticsCard.php create mode 100644 src/Application/UI/Livewire/Analytics/GoogleSearchConsole/GoogleSearchConsoleCard.php create mode 100644 src/Application/UI/Livewire/Analytics/GoogleTags/GoogleTagsCard.php create mode 100644 src/Application/UI/Livewire/Analytics/PixelMeta/PixelMetaCard.php create mode 100644 src/Application/UI/Livewire/Blog/Article/BlogArticleOffcanvasForm.php create mode 100644 src/Application/UI/Livewire/Blog/Article/BlogArticlesTable.php create mode 100644 src/Application/UI/Livewire/Blog/Category/BlogCategoriesTable.php create mode 100644 src/Application/UI/Livewire/Blog/Category/BlogCategoryOffcanvasForm.php create mode 100644 src/Application/UI/Livewire/Blog/Comment/BlogCommentOffcanvasForm.php create mode 100644 src/Application/UI/Livewire/Blog/Comment/BlogCommentsTable.php create mode 100644 src/Application/UI/Livewire/Blog/Tag/BlogTagOffcanvasForm.php create mode 100644 src/Application/UI/Livewire/Blog/Tag/BlogTagsTable.php create mode 100644 src/Application/UI/Livewire/Comunication/Messenger/MessengerCard.php rename Livewire/VuexyWebsiteAdmin/ChatSettings.php => src/Application/UI/Livewire/Comunication/TawkTo/TawkToCard.php (83%) rename Livewire/VuexyWebsiteAdmin/GoogleAnalyticsSettings.php => src/Application/UI/Livewire/Comunication/Twitter/TwitterCard.php (81%) create mode 100644 src/Application/UI/Livewire/Comunication/Whatsapp/WhatsappCard.php rename Livewire/VuexyWebsiteAdmin/ContactFormSettings.php => src/Application/UI/Livewire/Contact/Form/ContactFormCard.php (81%) rename Livewire/VuexyWebsiteAdmin/ContactInfoSettings.php => src/Application/UI/Livewire/Contact/Info/ContactInfoCard.php (84%) rename Livewire/VuexyWebsiteAdmin/LocationSettings.php => src/Application/UI/Livewire/Contact/Info/ContactLocationCard.php (80%) create mode 100644 src/Application/UI/Livewire/Content/Faq/FaqIndex.php rename {Livewire => src/Application/UI/Livewire/Content}/Faq/FaqOffcanvasForm.php (92%) create mode 100644 src/Application/UI/Livewire/Content/Gallery/GalleryIndex.php rename Livewire/LegalNotices/LegalNoticesIndex.php => src/Application/UI/Livewire/Content/Legal/LegalIndex.php (91%) rename Livewire/LegalNotices/LegalNoticeOffCanvasForm.php => src/Application/UI/Livewire/Content/Legal/LegalOffCanvasForm.php (90%) create mode 100644 src/Application/UI/Livewire/Seo/Canonical/CanonicalIndex.php create mode 100644 src/Application/UI/Livewire/Seo/Jsonld/JsonldIndex.php create mode 100644 src/Application/UI/Livewire/Seo/Manifest/ManifestCard.php create mode 100644 src/Application/UI/Livewire/Seo/Robots/RobotsCard.php rename Livewire/SitemapManager/SitemapManagerIndex.php => src/Application/UI/Livewire/Seo/Sitemap/SitemapIndex.php (75%) rename {Livewire/SitemapManager => src/Application/UI/Livewire/Seo/Sitemap}/SitemapUrlOffcanvasForm.php (90%) create mode 100644 src/Application/UI/Livewire/Seo/SocialCards/SocialCardsIndex.php rename Livewire/VuexyWebsiteAdmin/LogoOnDarkBgSettings.php => src/Application/UI/Livewire/Settings/General/LogoOnDarkBgCard.php (64%) rename Livewire/VuexyWebsiteAdmin/LogoOnLightBgSettings.php => src/Application/UI/Livewire/Settings/General/LogoOnLightBgCard.php (64%) rename Livewire/VuexyWebsiteAdmin/WebsiteDescriptionSettings.php => src/Application/UI/Livewire/Settings/General/WebsiteDescriptionCard.php (54%) rename Livewire/VuexyWebsiteAdmin/WebsiteFaviconSettings.php => src/Application/UI/Livewire/Settings/General/WebsiteFaviconCard.php (78%) create mode 100644 src/Application/UI/Livewire/Settings/Indexing/IndexingCard.php rename Livewire/VuexyWebsiteAdmin/SocialMediaSettings.php => src/Application/UI/Livewire/Settings/Social/SocialCard.php (91%) create mode 100644 src/Application/UI/Livewire/Translate/Google/GoogleTanslateCard.php create mode 100644 src/Application/UIX/ConfigBuilders/Blog/ArticlesTableConfigBuilder.php create mode 100644 src/Application/UIX/ConfigBuilders/Blog/CategoriesTableConfigBuilder.php create mode 100644 src/Application/UIX/ConfigBuilders/Blog/CommentsTableConfigBuilder.php create mode 100644 src/Application/UIX/ConfigBuilders/Blog/TagsTableConfigBuilder.php create mode 100644 src/Application/UIX/ConfigBuilders/Faq/FaqTableConfigBuilder.php rename {Console => src/Console}/Commands/SitemapGenerate.php (97%) create mode 100644 src/Console/Commands/WebsiteCacheHelperCommand.php create mode 100644 src/Console/Commands/WebsiteContentHelperCommand.php create mode 100644 src/Console/Commands/WebsiteMenuHelperCommand.php create mode 100644 src/Console/Commands/WebsiteSeoHelperCommand.php create mode 100644 src/Database/Seeders/WebsiteContentSeeder.php create mode 100644 src/Database/Seeders/WebsiteMenuItemSeeder.php create mode 100644 src/Database/Seeders/WebsiteMenuSeeder.php create mode 100644 src/Database/Seeders/WebsiteSeoProfileSeeder.php create mode 100644 src/Database/Seeders/WebsiteSiteSeeder.php create mode 100644 src/Models/BlogArticle.php create mode 100644 src/Models/BlogCategory.php create mode 100644 src/Models/BlogComment.php create mode 100644 src/Models/BlogTag.php create mode 100644 src/Models/Faq.php rename {Models => src/Models}/FaqCategory.php (55%) create mode 100644 src/Models/SitemapIndexFile.php create mode 100644 src/Models/SitemapProfile.php create mode 100644 src/Models/SitemapRule.php create mode 100644 src/Models/SitemapUrl.php create mode 100644 src/Models/WebsiteContent.php create mode 100644 src/Models/WebsiteContentBlock.php create mode 100644 src/Models/WebsiteContentVersion.php create mode 100644 src/Models/WebsiteMenu.php create mode 100644 src/Models/WebsiteMenuItem.php create mode 100644 src/Models/WebsiteSeoProfile.php create mode 100644 src/Models/WebsiteSite.php create mode 100644 src/Providers/VuexyWebsiteAdminServiceProvider.php create mode 100644 src/Traits/Context/HasSiteContext.php create mode 100644 src/Website/Cache/RenderCacheInvalidator.php create mode 100644 src/Website/Cache/WebsiteRenderCacheService.php create mode 100644 src/Website/Enums/WebsiteSiteStatus.php create mode 100644 src/Website/Menu/WebsiteMenuRenderer.php create mode 100644 src/Website/Traits/Cache/HasRenderCache.php create mode 100644 src/Website/UX/Content/WebsiteBreadcrumbsBuilderService.php create mode 100644 src/Website/UX/Header/___VuexyQuicklinksBuilderService.php create mode 100644 src/Website/UX/Header/___VuexySearchBuilderService.php create mode 100644 src/Website/UX/Menu/___VuexyMenuRegistry.php create mode 100644 src/Website/UX/Menu/___WebsiteMenuBuilderService.php create mode 100644 src/Website/UX/Template/WebsiteVarsBuilderService.php create mode 100644 src/Website/UX/Template/___VuexyInterfaceService.php create mode 100644 src/koneko-vuexy.module.php diff --git a/.editorconfig b/.editorconfig index 8f0de65..078441f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,8 +3,8 @@ root = true [*] charset = utf-8 end_of_line = lf -indent_size = 4 indent_style = space +indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true @@ -14,5 +14,8 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 +[*.{js,json,ts,vue}] +indent_size = 2 + [docker-compose.yml] indent_size = 4 diff --git a/.gitattributes b/.gitattributes index 90a4e33..1805660 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,24 +1,40 @@ -# Normaliza los saltos de línea en diferentes SO +# Normaliza los saltos de línea para todos los sistemas operativos * text=auto eol=lf -# Reglas para archivos específicos +# Reglas de diferencia por tipo de archivo *.blade.php diff=html -*.css diff=css -*.html diff=html -*.md diff=markdown -*.php diff=php +*.css diff=css +*.html diff=html +*.js diff=javascript +*.ts diff=typescript +*.vue diff=html +*.md diff=markdown +*.php diff=php +*.json diff=json +*.yml diff=yaml +*.yaml diff=yaml +*.stub diff=php -# Evitar que estos archivos se exporten con Composer create-project -/.github export-ignore -/.gitignore export-ignore -/.git export-ignore -.gitattributes export-ignore -.editorconfig export-ignore -.prettierrc.json export-ignore -.prettierignore export-ignore -.eslintrc.json export-ignore -CHANGELOG.md export-ignore -CONTRIBUTING.md export-ignore -README.md export-ignore -composer.lock export-ignore -package-lock.json export-ignore +# Archivos que NO deben exportarse con Composer create-project +/.github export-ignore +/.gitignore export-ignore +/.git export-ignore +.gitattributes export-ignore +.editorconfig export-ignore +.prettierrc.json export-ignore +.prettierignore export-ignore +.eslintrc.json export-ignore +CHANGELOG.md export-ignore +CONTRIBUTING.md export-ignore +README.md export-ignore +phpunit.xml export-ignore +phpunit.xml.dist export-ignore +composer.lock export-ignore +package-lock.json export-ignore +vite.config.js export-ignore +tailwind.config.js export-ignore +webpack.mix.js export-ignore +tests/ export-ignore +resources/assets/ export-ignore +resources/sass/ export-ignore +node_modules export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b15c0d1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,25 @@ +# CODEOWNERS para koneko/laravel-vuexy-admin + +# Asignar todos los archivos a Arturo Corro Pacheco (mantenedor principal) +* @koneko-mx + +# Archivos legales y de documentación +/LICENSE* @koneko-mx +/NOTICE.md @koneko-mx +/README* @koneko-mx +/CHANGELOG* @koneko-mx +/.github/SECURITY.md @koneko-mx +/.github/CODE_OF_CONDUCT.md @koneko-mx +/.github/ISSUE_TEMPLATE/ @koneko-mx +/.github/PULL_REQUEST_TEMPLATE.md @koneko-mx + +# Código principal +/src/ @koneko-mx +/config/ @koneko-mx +/resources/ @koneko-mx +/routes/ @koneko-mx +/tests/ @koneko-mx + +# Archivos del paquete +/composer.json @koneko-mx +/package.json @koneko-mx diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e00e004 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +# Código de Conducta de Contribuyentes + +Como participantes y colaboradores de este proyecto, nos comprometemos a fomentar una comunidad abierta, inclusiva, y respetuosa. + +## Comportamiento Esperado + +- Usar un lenguaje amable e inclusivo. +- Respetar diferentes puntos de vista y experiencias. +- Aceptar con gracia las críticas constructivas. +- Enfocarse en lo que es mejor para la comunidad. + +## Comportamiento Inaceptable + +- Uso de lenguaje ofensivo o insultante. +- Conducta de acoso en público o privado. +- Comentarios despectivos relacionados con género, raza, orientación, religión, discapacidad o condición médica. + +## Aplicación + +Las violaciones a este código pueden ser reportadas a: **arturo@koneko.mx** + +Nos reservamos el derecho de advertir, suspender o expulsar a cualquier contribuyente que no respete este código. + +Inspirado en el [Contributor Covenant](https://www.contributor-covenant.org/) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e8ed90c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,10 @@ +--- +name: "FUNDING.yml" +about: "Opciones de patrocinio del proyecto" +title: "Patrocinio" +labels: [funding] +assignees: [] + +--- + +Opciones de patrocinio del proyecto \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..115f54f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: "🐛 Bug Report" +about: Reporta un error, fallo o comportamiento inesperado. +title: "[BUG] " +labels: [bug] +assignees: [] + +--- + +## Descripción +Describe claramente el problema. + +## Pasos para reproducir +1. Ir a '...' +2. Hacer clic en '...' +3. Ver error '...' + +## Comportamiento esperado +Una descripción clara y concisa de lo que debería pasar. + +## Capturas de pantalla +(Si aplica, añade imágenes para ayudar a explicar tu problema.) + +## Entorno +- Versión de Laravel: +- Versión del componente: +- Navegador / Sistema operativo: + +## Información adicional +Agrega cualquier otro dato que consideres relevante. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..570f677 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,51 @@ +--- +name: "🧩 Feature Request" +about: Proponer una nueva funcionalidad o mejora para el sistema +title: "[Feature] " +labels: ["feature", "enhancement"] +assignees: [] + +--- + +## 📌 Descripción + +Por favor, proporciona una descripción clara y concisa de la funcionalidad que deseas agregar. + +--- + +## 🤔 Motivación + +¿Cuál es el problema que esta nueva funcionalidad resolverá o qué mejora ofrecerá? + +--- + +## 🧩 Solución Propuesta + +Describe cómo debería funcionar la nueva característica. Si tienes una idea de la implementación técnica, inclúyela. + +--- + +## 🛠️ Requisitos Técnicos (opcional) + +- ¿Requiere cambios en base de datos? +- ¿Afecta al rendimiento? +- ¿Involucra cambios en la UI o API? +- ¿Es compatible con la versión actual del sistema? + +--- + +## 💡 Alternativas consideradas + +Si evaluaste otras soluciones, por favor menciónalas y explica por qué las descartaste. + +--- + +## 📎 Recursos adicionales + +Agrega enlaces, capturas de pantalla o documentación que respalden tu solicitud. + +--- + +## 👤 Autor(a) + +- GitHub: @ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4fb229d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,55 @@ +# 📝 Pull Request + +Gracias por tu contribución a **Koneko Laravel Vuexy Admin**. + +Por favor, completa la siguiente información para ayudarnos a revisar tu PR. + +--- + +## 📋 Descripción + + + +--- + +## 🚀 Cambios realizados + +- [ ] Bugfix 🐞 +- [ ] Nueva característica ✨ +- [ ] Mejora/refactorización 🔧 +- [ ] Documentación 📚 +- [ ] Tests 🧪 +- [ ] Otro (especificar): + +--- + +## ✅ Checklist + +- [ ] He probado los cambios localmente +- [ ] He ejecutado las migraciones correctamente (si aplica) +- [ ] No he incluido datos sensibles ni secretos +- [ ] Los tests existentes no fallan +- [ ] He actualizado documentación relevante (si aplica) +- [ ] Incluye etiquetas útiles (`good first issue`, `bug`, `enhancement`, etc.) + +--- + +## 📎 Referencias + + +Closes # + +--- + +## 🌍 Idioma base + +- [ ] Español 🇲🇽 +- [ ] Inglés 🌐 +- [ ] Ambos + +--- + +## 👤 Autor(a) + +- GitHub: @ +- Nombre opcional: diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..9e3ac22 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,11 @@ +# Reporte de Vulnerabilidades de Seguridad + +Agradecemos el interés por ayudarnos a mantener este proyecto seguro. + +Si encuentras una vulnerabilidad de seguridad, por favor repórtala de manera responsable enviando un correo a: + +**arturo@koneko.mx** + +No abras un issue público hasta que hayamos tenido tiempo de analizar el reporte y preparar una solución. + +Gracias por apoyar la seguridad del ecosistema Koneko ERP. diff --git a/.gitignore b/.gitignore index d07bec2..a0f224d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,60 @@ -/node_modules -/vendor -/.vscode -/.nova -/.fleet -/.phpactor.json +# ⚙️ Laravel Package Defaults +/vendor/ +composer.lock + +# 🧪 PHPUnit +.phpunit.result.cache /.phpunit.cache -/.phpunit.result.cache -/.zed -/.idea +phpunit.xml +phpunit.xml.dist + +# 🧹 Cache y logs +/.cache +/storage/*.key +/storage/pail +*.log +*.dump +*.bak +*.tmp +*.swp + +# 🔐 Entornos y configuraciones +.env +.env.* +auth.json +.phpactor.json +.php-cs-fixer.cache +phpstan.neon.local +homestead.yaml +Homestead.json + +# 🧱 Compilación frontend (Vite, Mix, Webpack, Tailwind) +/node_modules/ +node_modules +public/build/ +public/hot/ +public/storage/ +.vite + +# 🧪 Tests y mocks (solo si generas temporalmente) +/coverage/ +*.test.* +*.spec.* + +# 🛠️ IDEs y herramientas de desarrollo +/.idea/ +/.vscode/ +/.nova/ +/.zed/ +/.fleet/ +*.sublime-workspace +*.sublime-project + +# 📦 Archivos del sistema +.DS_Store +Thumbs.db + +# 🚀 Entornos de staging / producción +*.local.* +*.production.* +*.staging.* diff --git a/CHANGELOG.md b/CHANGELOG.md index 379710b..6a626eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,39 +1,39 @@ -# 📜 CHANGELOG - Laravel Vuexy Website Admin +# 📦 CHANGELOG -Este documento sigue el formato [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - -## [0.1.0] - ALPHA - 2024-03-05 - -### ✨ Added (Agregado) -- 🚀 Primera versión alpha de la librería. -- 🔹 Implementación inicial de [funcionalidad clave 1]. -- 🔹 Integración con [dependencia o servicio principal]. -- 🔹 Soporte para [Laravel/Vuexy Admin, si aplica]. - -### 🛠 Changed (Modificado) -- 🔄 Optimización de [código o estructura interna]. - -### 🐛 Fixed (Correcciones) -- 🐞 Correcciones iniciales en [migraciones, modelos, servicios, etc.]. +Todos los cambios importantes de este proyecto se documentarán en este archivo. +Este proyecto sigue [Semantic Versioning](https://semver.org/lang/es/). --- -## 📅 Próximos Cambios Planeados -- 📊 **Mejoras en [feature futuro]**. -- 🏪 **Compatibilidad con [Laravel 11, Vuexy, etc.]**. -- 📍 **Integración con [API o funcionalidad esperada]**. +## 🧪 Versión en desarrollo (`dev-develop`) + +Este módulo está en desarrollo activo. No se ha publicado una versión estable ni beta formal. + +**Añadido** + +- Registro de contenidos dinámicos en `website_contents` +- Sistema de perfiles SEO (`website_seo_profiles`) con soporte para JSON-LD, Open Graph y Twitter +- Menús públicos dinámicos (`website_menus`) +- Control de visibilidad por rol, flag, permisos y autenticación +- Soporte para múltiples sitios (`multi-site`) y variantes de página +- Soporte para `hreflang`, `manifest.json`, y sitemap +- Integración con módulos `vuexy-admin` y layouts frontales vía `template`, `variant`, `render_mode` + +**Notas** + +- Esta versión no es estable y está sujeta a cambios mayores. +- Compatibilidad mínima: Laravel 11.31, PHP 8.2 +- Requiere `laravel-vuexy-admin` (`dev-develop`) --- -**📌 Nota:** Esta es una versión **ALPHA**, aún en desarrollo. +## 🚀 Planeación de versiones + +- `1.0.0-beta`: Publicación de la versión beta inicial con funcionalidades completas del CMS +- `1.0.0`: Versión estable para producción --- -## 🔄 Sincronización de Cambios -Este `CHANGELOG.md` se actualiza primero en nuestro repositorio principal en **[Tea - Koneko Git](https://git.koneko.mx/koneko/laravel-vuexy-website-admin)** y luego se refleja en GitHub. -Los cambios recientes pueden verse antes en **Tea** que en **GitHub** debido a la sincronización automática. - ---- - -📅 Última actualización: **2024-03-05**. +## 📁 Histórico +*Ninguna versión publicada aún.* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3932692..ce2666c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,77 @@ -## 🔐 Acceso al Repositorio Privado +# 🤝 Guía para Contribuidores -Nuestro servidor Git en **Tea** tiene un registro cerrado. Para contribuir: +¡Gracias por tu interés en colaborar con **Koneko ERP**! +Este proyecto busca ofrecer una suite profesional de administración web y ERP orientado al mercado mexicano y LATAM, bajo licencia **Business Source License 1.1** con transición futura a MIT. -1. Abre un **Issue** en [GitHub](https://github.com/koneko-mx/laravel-vuexy-website-admin/issues) indicando tu interés en contribuir. -2. Alternativamente, envía un correo a **contacto@koneko.mx** solicitando acceso. -3. Una vez aprobado, recibirás una invitación para registrarte y clonar el repositorio. +--- -Si solo necesitas acceso de lectura, puedes clonar la versión pública en **GitHub**. +## 📌 Requisitos básicos + +* Conocimiento de **PHP 8.2+** y **Laravel 11** +* Respeto por las buenas prácticas, código limpio y trabajo en equipo +* Preferentemente, experiencia en: + + * Livewire + * Spatie Permissions & Roles + * Laravel Fortify y Sanctum + * Desarrollo modular con Composer + +--- + +## 🌱 Primeros pasos + +1. **Forkea** este repositorio y clónalo localmente. + +2. Instala las dependencias con Composer: + + ```bash + composer install + ``` + +3. Copia el archivo `.env.example` a `.env` y ajusta tus credenciales locales: + + ```bash + cp .env.example .env + php artisan key:generate + ``` + +4. Ejecuta las migraciones y seeders (si aplica): + + ```bash + php artisan migrate --seed + ``` + +5. Inicia el servidor: + + ```bash + php artisan serve + ``` + +--- + +## 📀 Convenciones del Proyecto + +Antes de contribuir, asegúrate de leer nuestras +📁 [Convenciones de Componentes](CONVENTIONS.md), +donde definimos estructura de carpetas, estilo de código y otras buenas prácticas clave para mantener la consistencia del ecosistema **Koneko ERP**. + +--- + +## 📝 Código de Conducta + +Consulta nuestro [Código de Conducta](CODE_OF_CONDUCT.md) para conocer las expectativas y reglas que garantizan un ambiente colaborativo, profesional y respetuoso. + +--- + +## 🚀 ¡Listo para colaborar! + +Una vez que tengas tu entorno configurado: + +* Busca issues etiquetados como `good first issue` o `help wanted` +* Lee el archivo [`CONTRIBUTING.md`](CONTRIBUTING.md) del módulo específico si estás trabajando en un subpaquete +* No olvides seguir las convenciones de estilo PSR-12 y usar `php-cs-fixer` si lo tienes disponible + +--- + +Gracias por ser parte de **Koneko ERP** 💙 +¡Esperamos tu contribución! diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 0000000..909b433 --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,105 @@ +# ![Koneko ERP](https://git.koneko.mx/koneko-st/koneko-st/raw/branch/main/logo-images/horizontal-05.png) Convenciones de Estructura de Componentes + +📅 *Última actualización:* 2025-04-03 +🔧 *Aplicable a todos los módulos Composer de Koneko ERP* + +--- + +## 📁 Estructura General de un Componente + +```plaintext +component-root/ +├── config/ ← Configuraciones del módulo +├── Database/ +│ ├── data/ ← Archivos JSON, CSV, XLSX +│ ├── factories/ ← Factories para testing y seeders +│ ├── migrations/ ← Migraciones del esquema del módulo +│ └── Seeders/ ← Seeders base y de datos fake +├── Enums/ ← Enums (PSR-4) usados por el módulo +├── Events/ ← Eventos del módulo +├── Http/ +│ ├── Controllers/ ← Controladores +│ └── Middleware/ ← Middlewares específicos del módulo +├── Livewire/ ← Componentes Livewire organizados por dominio +├── Models/ ← Modelos Eloquent +├── Notifications/ ← Notificaciones personalizadas +├── Providers/ ← Service Providers del módulo +├── Services/ ← Servicios (lógica de negocio) +├── Support/ +│ ├── Base/ ← Clases base abstractas +│ ├── Builders/ ← Configuradores de vistas tipo índice +│ ├── Macros/ ← Macros de Str, Collection, etc. +│ ├── Queries/ ← Query Builders avanzados +│ ├── Registries/ ← Registro dinámico de configuración +│ └── Validation/ ← Validaciones personalizadas +├── Traits/ +│ ├── Audit/ ← Traits para auditoría y tracking +│ ├── Metadata/ ← Traits para metadatos del modelo +│ ├── Users/ ← Traits relacionados con usuarios +│ └── Indexing/ ← Traits usados por configuradores de índices +├── resources/ +│ ├── assets/ ← JS, SCSS, íconos o fuentes específicos +│ ├── faker-images/ ← Imágenes utilizadas en datos de prueba +│ ├── lang/ ← Archivos de traducción +│ └── views/ ← Vistas Blade +├── routes/ +│ └── admin.php ← Rutas internas del módulo +├── storage/ ← Recursos adicionales (ej. fuentes) +└── README.md ← Documentación del componente +``` + +--- + +## 🧠 Convenciones Generales + +- Todos los módulos deben seguir PSR-4. +- Los archivos deben nombrarse en *PascalCase* excepto `config/*.php` y rutas. +- Los `Seeder` deben ser agrupados por módulo si el componente los agrupa (ej. `vuexyAdmin`, `vuexyWarehouse`). +- Los `Factory` deben ser compatibles con `SeederWithFakeImages`. + +--- + +## 🖼️ Imágenes Faker + +- Carpeta: `resources/faker-images/` +- Subcarpetas válidas: `users/`, `stores/`, `products/`, etc. +- Las imágenes se usan exclusivamente para entornos de testing/demostración. +- Nunca se publican al frontend ni se exponen directamente. + +--- + +## 🧪 Factories + +- Todas las `factories` deben estar en `Database/factories/`. +- Si se extiende un modelo (`Koneko\VuexyAdmin\Models\User`), usar `new (User::class)` dinámico. +- Compatible con `SeederOrchestrator` y `config/seeder.php`. + +--- + +## 📊 Configuradores de Índice + +- Los index deben implementar `BaseModelIndexConfig` o su extensión. +- Pueden usar Traits como `HandlesFactory`, `HandlesIndexColumns`, `HandlesQueryBuilder`, etc. +- Se recomienda usar `Support/Builders/` para los configuradores y `Support/Registries/` si son extendibles. + +--- + +## 📚 Traducciones + +- Usar `resources/lang/es/` con archivos separados por dominio (`auth.php`, `validation.php`, etc.). +- `es_MX.json` puede usarse para traducciones inline. + +--- + +## 📌 Tips + +- Si un componente tiene `Service`, `Seeder`, `Factory` y `Livewire`, deben estar todos organizados en sus carpetas respectivas. +- La estructura del componente debe ser lo suficientemente clara para no depender de documentación externa. + +--- + +## 🤝 ¿Dudas o sugerencias? + +Este documento está en constante mejora. Si tienes sugerencias o deseas proponer mejoras, por favor abre un [issue de tipo mejora](.github/ISSUE_TEMPLATE/feature_request.md) o contribuye con un Pull Request. + +--- diff --git a/Http/Controllers/ChatController.php b/Http/Controllers/ChatController.php deleted file mode 100644 index c00f22d..0000000 --- a/Http/Controllers/ChatController.php +++ /dev/null @@ -1,19 +0,0 @@ - 'Acciones', - 'status' => 'Estatus', - 'created_at' => 'Fecha de Creación', - 'updated_at' => 'Última Actualización', - ]; - } - - /** - * Define los formatos de cada columna (se inyectará en $bt_datatable['format']). - * - * @return array - */ - protected function format(): array - { - return [ - 'action' => [ - 'formatter' => 'FaqActionFormatter', - 'onlyFormatter' => true, - ], - - 'status' => [ - 'formatter' => [ - 'name' => 'dynamicBooleanFormatter', - 'params' => ['tag' => 'activo'] - ], - 'align' => 'center', - ], - 'created_at' => [ - 'formatter' => 'textNowrapFormatter', - 'align' => 'center', - 'visible' => false, - ], - 'updated_at' => [ - 'formatter' => 'textNowrapFormatter', - 'align' => 'center', - 'visible' => false, - ], - ]; - } - - /** - * Retorna la configuración base (común) para la tabla Bootstrap Table. - * - * @return array - */ - protected function bootstraptableConfig(): array - { - return [ - 'sortName' => 'code', - 'exportFileName' => 'Almacenes', - 'showFullscreen' => false, - 'showPaginationSwitch' => false, - 'showRefresh' => false, - 'pagination' => false, - ]; - } - - /** - * Retorna la ruta de la vista Blade. - * - * @return string - */ - protected function viewPath(): string - { - // La vista que ya tienes creada para FaqIndex - return 'vuexy-website-admin::livewire.faq.index'; - } - - /** - * Métodos que necesites sobreescribir o extender. - */ - public function mount(): void - { - parent::mount(); - } -} diff --git a/Livewire/Images/ImagesIndex.php b/Livewire/Images/ImagesIndex.php deleted file mode 100644 index 9b30650..0000000 --- a/Livewire/Images/ImagesIndex.php +++ /dev/null @@ -1,104 +0,0 @@ - 'Acciones', - 'status' => 'Estatus', - 'created_at' => 'Fecha de Creación', - 'updated_at' => 'Última Actualización', - ]; - } - - /** - * Define los formatos de cada columna (se inyectará en $bt_datatable['format']). - * - * @return array - */ - protected function format(): array - { - return [ - 'action' => [ - 'formatter' => 'ImagesActionFormatter', - 'onlyFormatter' => true, - ], - - 'status' => [ - 'formatter' => [ - 'name' => 'dynamicBooleanFormatter', - 'params' => ['tag' => 'activo'] - ], - 'align' => 'center', - ], - 'created_at' => [ - 'formatter' => 'textNowrapFormatter', - 'align' => 'center', - 'visible' => false, - ], - 'updated_at' => [ - 'formatter' => 'textNowrapFormatter', - 'align' => 'center', - 'visible' => false, - ], - ]; - } - - /** - * Retorna la configuración base (común) para la tabla Bootstrap Table. - * - * @return array - */ - protected function bootstraptableConfig(): array - { - return [ - 'sortName' => 'code', - 'exportFileName' => 'Almacenes', - 'showFullscreen' => false, - 'showPaginationSwitch' => false, - 'showRefresh' => false, - 'pagination' => false, - ]; - } - - /** - * Retorna la ruta de la vista Blade. - * - * @return string - */ - protected function viewPath(): string - { - // La vista que ya tienes creada para ImagesIndex - return 'vuexy-website-admin::livewire.images.index'; - } - - /** - * Métodos que necesites sobreescribir o extender. - */ - public function mount(): void - { - parent::mount(); - } -} diff --git a/Livewire/LegalNotices/LegalSettings.php b/Livewire/LegalNotices/LegalSettings.php deleted file mode 100644 index 0f82f39..0000000 --- a/Livewire/LegalNotices/LegalSettings.php +++ /dev/null @@ -1,108 +0,0 @@ - 'save', - ]; - - public function mount() - { - $this->loadSettings(); - - // Seleccionar la primera sección por defecto - $this->currentSection = array_key_first($this->legalVars); - } - - function loadSettings() - { - $websiteTemplateService = app(WebsiteTemplateService::class); - - switch ($this->currentSection) { - case 'legal_terminos_y_condiciones': - $this->legalVars['legal_terminos_y_condiciones'] = $websiteTemplateService->getLegalVars('legal_terminos_y_condiciones'); - break; - - case 'legal_aviso_de_privacidad': - $this->legalVars['legal_aviso_de_privacidad'] = $websiteTemplateService->getLegalVars('legal_aviso_de_privacidad'); - break; - - case 'legal_politica_de_devoluciones': - $this->legalVars['legal_politica_de_devoluciones'] = $websiteTemplateService->getLegalVars('legal_politica_de_devoluciones'); - break; - - case 'legal_politica_de_envios': - $this->legalVars['legal_politica_de_envios'] = $websiteTemplateService->getLegalVars('legal_politica_de_envios'); - break; - - case 'legal_politica_de_cookies': - $this->legalVars['legal_politica_de_cookies'] = $websiteTemplateService->getLegalVars('legal_politica_de_cookies'); - break; - - case 'legal_autorizaciones_y_licencias': - $this->legalVars['legal_autorizaciones_y_licencias'] = $websiteTemplateService->getLegalVars('legal_autorizaciones_y_licencias'); - break; - - case 'legal_informacion_comercial': - $this->legalVars['legal_informacion_comercial'] = $websiteTemplateService->getLegalVars('legal_informacion_comercial'); - break; - - case 'legal_consentimiento_para_el_login_de_terceros': - $this->legalVars['legal_consentimiento_para_el_login_de_terceros'] = $websiteTemplateService->getLegalVars('legal_consentimiento_para_el_login_de_terceros'); - break; - - case 'legal_leyendas_de_responsabilidad': - $this->legalVars['legal_leyendas_de_responsabilidad'] = $websiteTemplateService->getLegalVars('legal_leyendas_de_responsabilidad'); - break; - - default: - $this->legalVars = $websiteTemplateService->getLegalVars(); - } - } - - public function rules() - { - $rules = []; - - if ($this->legalVars[$this->currentSection]['enabled']) { - $rules["legalVars.{$this->currentSection}.content"] = ['required', 'string', new NotEmptyHtml]; - } - - $rules["legalVars.{$this->currentSection}.enabled"] = 'boolean'; - - return $rules; - } - - public function save() - { - $this->validate($this->rules()); - - $websiteSettingsService = app(WebsiteSettingsService::class); - $websiteSettingsService->updateSetting($this->currentSection . '_enabled', $this->legalVars[$this->currentSection]['enabled']); - $websiteSettingsService->updateSetting($this->currentSection . '_content', $this->legalVars[$this->currentSection]['content']); - - $this->dispatch( - 'notification', - target: $this->targetNotify, - type: 'success', - message: 'Se han guardado los cambios en las configuraciones.' - ); - } - - public function render() - { - return view('admin::livewire.website-settings.legal-settings'); - } -} diff --git a/Models/Faq.php b/Models/Faq.php deleted file mode 100644 index 90af6ab..0000000 --- a/Models/Faq.php +++ /dev/null @@ -1,33 +0,0 @@ - 'integer', - 'is_active' => 'boolean', - ]; - - /** - * Categoría a la que pertenece esta FAQ. - */ - public function category(): BelongsTo - { - return $this->belongsTo(FaqCategory::class, 'category_id'); - } -} diff --git a/Models/SitemapUrl.php b/Models/SitemapUrl.php deleted file mode 100644 index dffb3b0..0000000 --- a/Models/SitemapUrl.php +++ /dev/null @@ -1,19 +0,0 @@ -loadRoutesFrom(__DIR__.'/../routes/admin.php'); - - - // Cargar vistas del paquete - $this->loadViewsFrom(__DIR__.'/../resources/views', 'vuexy-website-admin'); - - - // Register the migrations - $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); - - // Registrar comandos de consola - if ($this->app->runningInConsole()) { - $this->commands([ - SitemapGenerate::class, - ]); - } - - - // Registrar Livewire Components - $components = [ - // ajustes generales - 'vuexy-website-admin::website-description-settings' => WebsiteDescriptionSettings::class, - 'vuexy-website-admin::website-favicon-settings' => WebsiteFaviconSettings::class, - 'vuexy-website-admin::logo-on-light-bg-settings' => LogoOnLightBgSettings::class, - 'vuexy-website-admin::logo-on-dark-bg-settings' => LogoOnDarkBgSettings::class, - - // Avisos legales - 'vuexy-website-admin::legal-notices-index' => LegalNoticesIndex::class, - 'vuexy-website-admin::legal-notice-offcanvas-form' => LegalNoticeOffCanvasForm::class, - - // Preguntas frecuentes - 'vuexy-website-admin::faq-index' => FaqIndex::class, - 'vuexy-website-admin::faq-offcanvas-form' => FaqOffCanvasForm::class, - - // Redes sociales - 'vuexy-website-admin::social-media-settings' => SocialMediaSettings::class, - - // Chat - 'vuexy-website-admin::chat-settings' => ChatSettings::class, - - // Galería de imágenes - 'vuexy-website-admin::images-index' => ImagesIndex::class, - - // Google Analytics - 'vuexy-website-admin::google-analytics-settings' => GoogleAnalyticsSettings::class, - - // Información de contacto - 'vuexy-website-admin::contact-info-settings' => ContactInfoSettings::class, - 'vuexy-website-admin::location-settings' => LocationSettings::class, - - // Formulario de contacto - 'vuexy-website-admin::contact-form-settings' => ContactFormSettings::class, - - // Mapa del sitio - 'vuexy-website-admin::sitemap-manager-index' => SitemapManagerIndex::class, - 'vuexy-website-admin::sitemap-manager-offcanvas-form' => SitemapUrlOffcanvasForm::class, - ]; - - foreach ($components as $alias => $component) { - Livewire::component($alias, $component); - } - - } -} diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..9ed1226 --- /dev/null +++ b/README.en.md @@ -0,0 +1,118 @@ +# 🧩 Laravel Vuexy Website Admin + +

+ + Koneko Soluciones Tecnológicas Logo + +

+ +

+ Website + Latest Version + License + Issues +

+ +--- + +## 📌 Description + +**Laravel Vuexy Website Admin** is a core plugin in the **Koneko ERP** ecosystem. Built on Laravel 11 and integrated with the official `laravel-vuexy-admin` system, this package provides a powerful content management interface for corporate and eCommerce websites. + +It enables complete control over site configuration, SEO optimization, domain management, multilingual support, and template rendering. The module follows modern coding standards and is compatible with Redis, PostgreSQL, and multi-site deployments. + +--- + +### 📦 Main Features + +* Multi-site and multi-domain support +* Site-level settings and branding configuration +* SEO profile management with OpenGraph, Twitter Cards, JSON-LD, and canonical settings +* Sitemap, robots.txt, and manifest.json integration +* CMS with dynamic blocks, reusable menus, and content versioning +* Full page and block-based caching system +* Template selector and preview system +* Blog module with categories, tags, articles, and comment moderation +* API integration for Google, Meta, WhatsApp, Tawk.to, and more +* Translation and localization tools (DeepL, Google Translate) + +--- + +### 🚀 Quick Installation + +```bash +composer require koneko/laravel-vuexy-website-admin +php artisan migrate --seed +php artisan vendor:publish --tag=vuexy-website-admin-config +``` + +> You must have `laravel-vuexy-admin` installed before using this plugin. + +--- + +### 📦 Included Commands + +```bash +php artisan website:seo-helper +php artisan website:menu-helper +php artisan website:content-helper +php artisan website:cache-helper +php artisan website:sitemap-generate +``` +--- + +### ⚙️ Publicación de archivos + +This package publishes: + +* Routes, views and Livewire components +* Configuration and permissions files (RBAC) +* Artisan commands and content generators +* Menu and modular system extensions + +```bash +php artisan vendor:publish --tag=vuexy-website-admin-config +``` + +--- + +### 🔧 Uso y personalización + +This module allows you to customize its structure and behavior using: + +* Middlewares for web and content context +* Multisite configuration with support for dynamic templates +* Decoupled view system compatible with Blade, Vite and partial rendering + +You can extend or override any published view, layout or Livewire component. + +--- + +### 🛠️ Requirements + +* PHP `^8.2` +* Laravel `^11.31` +* [koneko/laravel-vuexy-admin](https://github.com/koneko-mx/laravel-vuexy-admin) install and configured + +--- + +## 📄 License + +This package is licensed under the [custom Business Source License 1.1](LICENSE), transitioning to MIT after 3 years. For commercial usage, redistribution, or extended usage, please contact: + +📧 [opensource@koneko.mx](mailto:opensource@koneko.mx) + +--- + +## 📚 More Information + +* [Vuexy Admin Core](https://github.com/koneko-mx/laravel-vuexy-admin) +* [Documentation in Spanish](README.md) +* [Koneko ST Official Website](https://koneko.mx) +* [Contact Email](mailto:opensource@koneko.mx) + +--- + +

+ Made with ❤️ in Mexico by Koneko Soluciones Tecnológicas +

diff --git a/README.md b/README.md index ba91b9f..26af72f 100644 --- a/README.md +++ b/README.md @@ -1,133 +1,116 @@ -# 🎨 Laravel Vuexy Website Admin - Vuexy Admin +# 🧩 Laravel Vuexy Website Admin

- Koneko Soluciones Tecnológicas Logo + + Koneko Soluciones Tecnológicas Logo +

+

- Sitio Web - Latest Stable Version - License - Servidor Git - Build Status - Issues + Sitio Web + Versión estable + Licencia + Issues

--- ## 📌 Descripción -**Laravel Vuexy Website Admin** es un módulo diseñado para **Laravel Vuexy Admin**, proporcionando [breve descripción de la funcionalidad]. +**Laravel Vuexy Website Admin** es un módulo del ecosistema **Koneko ERP** desarrollado en Laravel 11, orientado a la administración de contenido web multisitio, multidioma y multitemplate. Forma parte del stack web profesional de Koneko, integrando funcionalidades CMS, SEO, blog, renderización por bloques, cache HTML y plantillas dinámicas. -### ✨ Características: -- 🔹 Integración completa con Vuexy Admin. -- 🔹 Funcionalidad clave 1. -- 🔹 Funcionalidad clave 2. +Está diseñado para integrarse de forma transparente al backend Vuexy Admin y utilizarse con frontends basados en Vite y plantillas como **Porto, Landwind, Notus** y otras. --- -## 📦 Instalación +### 📦 Características -Instalar vía **Composer**: +* Gestión de múltiples sitios web y dominios +* Configuración contextual del sitio (branding, idioma, indexación, manifest.json, etc.) +* Editor de contenido por bloques y plantillas Blade +* Administración de menús, páginas, SEO y JSON-LD +* Blog con artículos, etiquetas, categorías y comentarios +* Sistema de caché de contenido completo y por bloque +* Integraciones con APIs externas como Google Analytics, Pixel Meta, Tawk.to, etc. + +--- + +### 🚀 Instalación rápida ```bash composer require koneko/laravel-vuexy-website-admin -``` - -Publicar archivos de configuración y migraciones (si aplica): - -```bash -php artisan vendor:publish --tag=laravel-vuexy-website-admin-config -php artisan migrate -``` - ---- - -## 🚀 Uso básico - -```php -use Koneko\NombreLibreria\Models\Model; - -$model = Model::create([ - 'campo1' => 'Valor', - 'campo2' => 'Otro valor', -]); -``` - ---- - -## 📚 Configuración adicional - -Si necesitas personalizar la configuración del módulo, publica el archivo de configuración: - -```bash -php artisan vendor:publish --tag=laravel-vuexy-website-admin-config -``` - -Esto generará `config/nombre_libreria.php`, donde puedes modificar valores predeterminados. - ---- - -## 🛠 Dependencias - -Este paquete requiere las siguientes dependencias: -- Laravel 11 -- `koneko/laravel-vuexy-website-admin` -- Dependencias específicas de la librería - ---- - -## 📦 Publicación de Assets y Configuraciones - -Para publicar configuraciones y seeders: - -```bash -php artisan vendor:publish --tag=laravel-vuexy-website-admin-config -php artisan vendor:publish --tag=laravel-vuexy-website-admin-seeders php artisan migrate --seed +php artisan vendor:publish --tag=vuexy-website-admin-config ``` -Para publicar imágenes del tema: +> Debes tener instalado `laravel-vuexy-admin` antes de usar este complemento. + +--- + +### 📦 Comandos Incluidos ```bash -php artisan vendor:publish --tag=laravel-vuexy-website-admin-images +php artisan website:seo-helper +php artisan website:menu-helper +php artisan website:content-helper +php artisan website:cache-helper +php artisan website:sitemap-generate ``` --- -## 🛠 Pruebas +### ⚙️ Publicación de archivos -Ejecuta los tests con: +Este paquete publica: + +* Rutas, vistas y Livewire components +* Archivos de configuración y permisos (RBAC) +* Comandos Artisan y generadores de contenido +* Extensiones para menú y sistema modular ```bash -php artisan test +php artisan vendor:publish --tag=vuexy-website-admin-config ``` --- -## 🌍 Repositorio Principal y Sincronización +### 🔧 Uso y personalización -Este repositorio es una **copia sincronizada** del repositorio principal alojado en **[Tea - Koneko Git](https://git.koneko.mx/koneko/laravel-vuexy-website-admin)**. +Este módulo permite personalizar su estructura y comportamiento utilizando: -### 🔄 Sincronización con GitHub -- **Repositorio Principal:** [git.koneko.mx](https://git.koneko.mx/koneko/laravel-vuexy-website-admin) -- **Repositorio en GitHub:** [github.com/koneko/laravel-vuexy-website-admin](https://github.com/koneko/laravel-vuexy-website-admin) -- **Los cambios pueden reflejarse primero en Tea antes de GitHub.** +* Middlewares para contexto web y contenido +* Configuración multisitio con soporte para templates dinámicos +* Sistema de vistas desacoplado compatible con Blade, Vite y renderizado parcial -### 🤝 Contribuciones -Si deseas contribuir: -1. Puedes abrir un **Issue** en [GitHub Issues](https://github.com/koneko/laravel-vuexy-website-admin/issues). -2. Para Pull Requests, **preferimos contribuciones en Tea**. Contacta a `admin@koneko.mx` para solicitar acceso. - -⚠️ **Nota:** Algunos cambios pueden tardar en reflejarse en GitHub, ya que este repositorio se actualiza automáticamente desde Tea. +Puedes extender o sobreescribir cualquier vista, layout o componente Livewire publicado. --- -## 🏅 Licencia +## 🛠️ Requisitos -Este paquete es de código abierto bajo la licencia [MIT](LICENSE). +* PHP `^8.2` +* Laravel `^11.31` +* [koneko/laravel-vuexy-admin](https://github.com/koneko-mx/laravel-vuexy-admin) instalado y configurado + +--- + +## 📄 Licencia + +Este paquete se distribuye bajo la [Licencia Business Source 1.1 personalizada](LICENSE.es), con transición automática a MIT a los 3 años. Para uso comercial, redistribución o integraciones ampliadas, contacta a: + +📧 [opensource@koneko.mx](mailto:opensource@koneko.mx) + +--- + +## 📚 Más Información + +* [Core Vuexy Admin](https://github.com/koneko-mx/laravel-vuexy-admin) +* [Documentación en inglés](README.en.md) +* [Sitio Oficial Koneko ST](https://koneko.mx) +* [Correo de Contacto](mailto:opensource@koneko.mx) ---

- Hecho con ❤️ por Koneko Soluciones Tecnológicas + Hecho con ❤️ en México por Koneko Soluciones Tecnológicas

diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e733e2a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# 🛡️ Política de Seguridad + +Koneko Soluciones Tecnológicas se toma muy en serio la seguridad de este proyecto y de sus usuarios. + +## 🔒 Reporte responsable de vulnerabilidades + +Si encuentras alguna vulnerabilidad de seguridad, **no abras un issue público**. En lugar de ello, por favor repórtala de forma privada por cualquiera de los siguientes medios: + +- 📧 Correo oficial: [opensource@koneko.mx](mailto:opensource@koneko.mx) + +Por favor incluye los siguientes detalles: + +- Descripción del problema +- Versión afectada del paquete +- Instrucciones claras para reproducir el fallo (si es posible) + +Nos comprometemos a responder dentro de **5 días hábiles**. + +## 🛡️ Alcance + +Este procedimiento aplica únicamente al paquete: + +- `koneko/laravel-vuexy-admin` +Y a sus repositorios relacionados dentro del ecosistema oficial Koneko. + +## ✅ Reconocimientos + +Se podrá otorgar reconocimiento público (con tu permiso) en futuras notas de versión a quienes reporten vulnerabilidades de manera responsable. + +Gracias por ayudar a hacer más seguro el ecosistema de Koneko ERP. diff --git a/composer.json b/composer.json index 0253bf3..201ee1f 100644 --- a/composer.json +++ b/composer.json @@ -1,17 +1,20 @@ { "name": "koneko/laravel-vuexy-website-admin", - "description": "Laravel Vuexy Website Admin, un modulo de administracion de sitios web.", - "keywords": ["laravel", "koneko", "framework", "vuexy", "website", "admin", "mexico"], + "description": "Web content management module for Laravel projects, integrated with Vuexy Admin and optimized for professional sites in Mexico.", + "keywords": [ + "laravel", "cms", "website-admin", "vuexy", "koneko", "mexico", "admin-panel", + "seo", "gestión de contenidos", "multi-site", "erp" + ], "type": "library", - "license": "MIT", + "license": "BUSL-1.1-custom", "require": { "php": "^8.2", - "koneko/laravel-vuexy-admin": "dev-main", + "koneko/laravel-vuexy-admin": "dev-develop", "laravel/framework": "^11.31" }, "autoload": { "psr-4": { - "Koneko\\VuexyWebsiteAdmin\\": "./" + "Koneko\\VuexyWebsiteAdmin\\": "src/" } }, "extra": { @@ -31,5 +34,6 @@ "source": "https://github.com/koneko-mx/laravel-vuexy-website-admin", "issues": "https://github.com/koneko-mx/laravel-vuexy-website-admin/issues" }, + "minimum-stability": "dev", "prefer-stable": true } diff --git a/config/koneko-website.php b/config/koneko-website.php new file mode 100644 index 0000000..e4046ff --- /dev/null +++ b/config/koneko-website.php @@ -0,0 +1,28 @@ + [ + 'enabled' => (bool) env('KONEKO_WEBSITE_CACHE_ENABLED', true), + 'ttl' => (int) env('KONEKO_WEBSITE_CACHE_TTL', 20 * 24 * 60), + ], + 'menu' => [ + 'cache' => [ + 'enabled' => (bool) env('VUEXY_WEBSITE_MENU_CACHE_ENABLED', true), + 'ttl' => (int) env('VUEXY_WEBSITE_MENU_CACHE_TTL', 3600), + ], + 'debug' => [ + 'show_broken_routes' => (bool) env('VUEXY_WEBSITE_MENU_DEBUG_SHOW_BROKEN_ROUTES', false), + 'show_disallowed_links' => (bool) env('VUEXY_WEBSITE_MENU_DEBUG_SHOW_DISALLOWED_LINKS', false), + ], + ], + 'html' => [ + 'cache' => [ + 'enabled' => (bool) env('VUEXY_WEBSITE_HTML_CACHE_ENABLED', true), + 'ttl' => (int) env('VUEXY_WEBSITE_HTML_CACHE_TTL', 900), + ], + 'debug' => [ + 'mode' => (bool) env('VUEXY_WEBSITE_HTML_DEBUG_MODE', true), + ] + ] +]; diff --git a/config/vuexy_apis_catalog.php.back b/config/vuexy_apis_catalog.php.back new file mode 100644 index 0000000..d43bf91 --- /dev/null +++ b/config/vuexy_apis_catalog.php.back @@ -0,0 +1,108 @@ + [ + 'name' => 'Google Analytics', + 'slug' => 'google_analytics', + 'provider' => ApiProvider::Google, + 'auth_type' => ApiAuthType::OAuth2, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://analytics.googleapis.com', + 'scopes' => ['https://www.googleapis.com/auth/analytics.readonly'], + 'credentials' => [ + 'client_id' => env('GOOGLE_ANALYTICS_CLIENT_ID'), + 'client_secret' => env('GOOGLE_ANALYTICS_CLIENT_SECRET'), + 'redirect_uri' => env('GOOGLE_ANALYTICS_REDIRECT_URI', url('/oauth2/callback')), + ], + ], + + 'google_translate' => [ + 'name' => 'Google Translate', + 'slug' => 'google_translate', + 'provider' => ApiProvider::Google, + 'auth_type' => ApiAuthType::ApiKey, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://translation.googleapis.com/language/translate/v2', + 'credentials' => [ + 'api_key' => env('GOOGLE_TRANSLATE_API_KEY'), + ], + ], + + 'google_search_console' => [ + 'name' => 'Google Search Console', + 'slug' => 'google_search_console', + 'provider' => ApiProvider::Google, + 'auth_type' => ApiAuthType::OAuth2, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://searchconsole.googleapis.com', + 'scopes' => ['https://www.googleapis.com/auth/webmasters.readonly'], + 'credentials' => [ + 'client_id' => env('GOOGLE_SEARCH_CLIENT_ID'), + 'client_secret' => env('GOOGLE_SEARCH_CLIENT_SECRET'), + 'redirect_uri' => env('GOOGLE_SEARCH_REDIRECT_URI'), + ], + ], + + 'google_tag_manager' => [ + 'name' => 'Google Tag Manager', + 'slug' => 'google_tag_manager', + 'provider' => ApiProvider::Google, + 'auth_type' => ApiAuthType::OAuth2, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://www.googleapis.com/tagmanager/v2', + 'scopes' => ['https://www.googleapis.com/auth/tagmanager.readonly'], + 'credentials' => [ + 'client_id' => env('GOOGLE_TAG_CLIENT_ID'), + 'client_secret' => env('GOOGLE_TAG_CLIENT_SECRET'), + 'redirect_uri' => env('GOOGLE_TAG_REDIRECT_URI'), + ], + ], + + // ======================= META / FACEBOOK ======================= + + 'facebook_messenger' => [ + 'name' => 'Facebook Messenger', + 'slug' => 'facebook_messenger', + 'provider' => ApiProvider::Facebook, + 'auth_type' => ApiAuthType::OAuth2, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://graph.facebook.com/v17.0', + 'scopes' => ['pages_messaging', 'pages_show_list'], + 'credentials' => [ + 'app_id' => env('FACEBOOK_APP_ID'), + 'app_secret' => env('FACEBOOK_APP_SECRET'), + 'redirect_uri' => env('FACEBOOK_REDIRECT_URI'), + ], + ], + + // ======================= X / TWITTER ======================= + + 'twitter_api' => [ + 'name' => 'Twitter API v2', + 'slug' => 'twitter_api', + 'provider' => ApiProvider::Twitter, + 'auth_type' => ApiAuthType::BearerToken, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://api.twitter.com/2', + 'credentials' => [ + 'bearer_token' => env('TWITTER_BEARER_TOKEN'), + ], + ], + + // ======================= TAWK.TO ======================= + + 'tawk_to' => [ + 'name' => 'Tawk.to Live Chat', + 'slug' => 'tawk_to', + 'provider' => ApiProvider::Custom, + 'auth_type' => ApiAuthType::None, + 'environment' => ApiEnvironment::Production, + 'base_url' => 'https://embed.tawk.to', + 'credentials' => [], + ], +]; diff --git a/config/vuexy_website_admin_menu copy.php b/config/vuexy_website_admin_menu copy.php new file mode 100644 index 0000000..5cfa12b --- /dev/null +++ b/config/vuexy_website_admin_menu copy.php @@ -0,0 +1,339 @@ + [ + '_meta' => [ + 'icon' => 'ti ti-settings', + 'description' => 'Administra la configuración, contenido, integraciones y visibilidad de tu sitio web empresarial.', + 'widget_label' => 'Sitio Web y SEO', + 'priority' => 200, + ], + 'submenu' => [ + 'Configuración general' => [ + '_meta' => [ + 'icon' => 'ti ti-settings', + 'description' => 'Ajustes generales del sitio, redes sociales y configuración de indexado.', + 'widget_label' => 'Configuración del Sitio Web', + 'home_at_root' => true, + 'priority' => 100, + ], + 'submenu' => [ + 'Ajustes generales' => [ + 'icon' => 'ti ti-settings', + 'route' => 'admin.website-admin.settings.general.index', + 'can' => 'admin.website-admin.settings.general.view', + 'description' => 'Personaliza el título, favicon, y otros aspectos básicos del sitio.', + ], + 'Enlaces sociales' => [ + 'icon' => 'ti ti-share', + 'route' => 'admin.website-admin.settings.social.index', + 'can' => 'admin.website-admin.settings.social.view', + 'description' => 'Administra los enlaces y metadatos de redes sociales.', + ], + 'Visibilidad en buscadores' => [ + 'icon' => 'ti ti-search', + 'route' => 'admin.website-admin.settings.indexing.index', + 'can' => 'admin.website-admin.settings.indexing.view', + 'description' => 'Controla el indexado del sitio web por los motores de búsqueda.', + ], + ] + ], + 'Contacto' => [ + '_meta' => [ + 'icon' => 'ti ti-device-mobile-question', + 'description' => 'Información y formularios de contacto del sitio.', + 'widget_label' => 'Información de contacto', + 'home_at_root' => true, + 'priority' => 200, + ], + 'submenu' => [ + 'Información de contacto' => [ + 'icon' => 'ti ti-device-mobile-message', + 'route' => 'admin.website-admin.contact.info.index', + 'can' => 'admin.website-admin.contact.info.view', + 'description' => 'Dirección, teléfonos, correos y ubicación.', + ], + 'Formulario de contacto' => [ + 'icon' => 'ti ti-mail-cog', + 'route' => 'admin.website-admin.contact.form.index', + 'can' => 'admin.website-admin.contact.form.view', + 'description' => 'Configuración y campos del formulario de contacto.', + ], + ] + ], + 'Chat & Comunicación' => [ + '_meta' => [ + 'icon' => 'ti ti-message-dots', + 'description' => 'Soporte al cliente y canales de comunicación directa.', + 'widget_label' => 'Chat & Comunicación de Sitio Web', + 'home_at_root' => true, + 'priority' => 500, + ], + 'submenu' => [ + 'Facebook Messenger' => [ + 'icon' => 'ti ti-brand-messenger', + 'route' => 'admin.website-admin.comunication.messenger.index', + 'can' => 'admin.website-admin.comunication.messenger.view', + 'description' => 'Activa el chat de Messenger en tu sitio.', + ], + 'Whatsapp Chat' => [ + 'icon' => 'ti ti-brand-whatsapp', + 'route' => 'admin.website-admin.comunication.whatsapp.index', + 'can' => 'admin.website-admin.comunication.whatsapp.view', + 'description' => 'Integra un botón de chat directo con WhatsApp.', + ], + 'Tawk.to' => [ + 'icon' => 'ti ti-message-dots', + 'route' => 'admin.website-admin.comunication.tawk-to.index', + 'can' => 'admin.website-admin.comunication.tawk-to.view', + 'description' => 'Agrega soporte en vivo con Tawk.to.', + ], + 'Twitter API' => [ + 'icon' => 'ti ti-brand-x', + 'route' => 'admin.website-admin.comunication.twitter.index', + 'can' => 'admin.website-admin.comunication.twitter.view', + 'description' => 'Configura la integración con Twitter/X.', + ], + ] + ], + 'CMS - Gestor de Contenidos' => [ + '_meta' => [ + 'icon' => 'ti ti-layout-grid-add', + 'description' => 'Crea, edita y publica contenido dinámico basado en Blade para tu sitio web.', + 'widget_label' => 'Administrador de Contenidos CMS', + 'priority' => 250, + ], + 'submenu' => [ + 'Menús del sitio' => [ + 'icon' => 'ti ti-hierarchy-3', + 'route' => 'admin.website-admin.cms.menus.index', + 'can' => 'admin.website-admin.cms.menus.view', + 'description' => 'Administra la estructura de navegación y menús dinámicos.', + ], + 'Perfil SEO' => [ + 'icon' => 'ti ti-settings', + 'route' => 'admin.website-admin.cms.seo.index', + 'can' => 'admin.website-admin.cms.seo.view', + 'description' => 'Configura las metas y parámetros SEO del sitio.', + ], + 'Contenidos dinámicos' => [ + 'icon' => 'ti ti-file-text', + 'route' => 'admin.website-admin.cms.contents.index', + 'can' => 'admin.website-admin.cms.contents.view', + 'description' => 'Administra páginas, bloques y parciales Blade de tu sitio.', + ], + 'Versiones de contenido' => [ + 'icon' => 'ti ti-clock-edit', + 'route' => 'admin.website-admin.cms.versions.index', + 'can' => 'admin.website-admin.cms.versions.view', + 'description' => 'Gestiona las versiones de contenido y restauraciones.', + ], + 'Plantillas disponibles' => [ + 'icon' => 'ti ti-template', + 'route' => 'admin.website-admin.cms.templates.index', + 'can' => 'admin.website-admin.cms.templates.view', + 'description' => 'Define y organiza los templates base disponibles.', + ], + ], + ], + 'Cache' => [ + '_meta' => [ + 'icon' => 'ti ti-cpu', + 'description' => 'Administración y limpieza de caché para mejorar el rendimiento.', + 'widget_label' => 'Cache de Sitio Web', + 'home_at_root' => true, + 'priority' => 900, + ], + 'submenu' => [ + 'Cache HTML renderizado' => [ + 'icon' => 'ti ti-file-type-html', + 'description' => 'Visualiza y limpia la caché de HTML completo del sitio web.', + 'route' => 'admin.website-admin.cache.fullpage.index', + 'can' => 'admin.website-admin.cache.fullpage.view', + ], + 'Previsualizaciones firmadas' => [ + 'icon' => 'ti ti-lock-access', + 'description' => 'URLs de vista previa y control de firma temporal.', + 'route' => 'admin.website-admin.cache.signed-previews.index', + 'can' => 'admin.website-admin.cache.signed-previews.view', + ], + ] + ], + 'Traducciones e internacional' => [ + '_meta' => [ + 'icon' => 'ti ti-language', + 'description' => 'Herramientas para traducción automática del sitio.', + 'widget_label' => 'Herramientas de traducción de Sitio Web', + 'home_at_root' => true, + 'priority' => 500, + ], + 'submenu' => [ + 'Google Translate' => [ + 'icon' => 'ti ti-language', + 'route' => 'admin.website-admin.translate.google.index', + 'can' => 'admin.website-admin.translate.google.view', + 'description' => 'Activa la traducción automática con Google Translate.', + ], + ] + ], + 'Contenido' => [ + '_meta' => [ + 'icon' => 'ti ti-hierarchy', + 'description' => 'Maneja contenido informativo y visual.', + 'widget_label' => 'Contenidos del Sitio Web', + 'home_at_root' => true, + 'priority' => 400, + ], + 'submenu' => [ + 'Preguntas frecuentes' => [ + 'icon' => 'ti ti-bubble-text', + 'route' => 'admin.website-admin.content.faq.index', + 'can' => 'admin.website-admin.content.faq.view', + 'description' => 'Administra las preguntas frecuentes del sitio.', + ], + 'Galería de imágenes' => [ + 'icon' => 'ti ti-photo', + 'route' => 'admin.website-admin.content.gallery.index', + 'can' => 'admin.website-admin.content.gallery.view', + 'description' => 'Agrega y organiza tus imágenes.', + ], + 'Avisos legales' => [ + 'icon' => 'ti ti-file-text-shield', + 'route' => 'admin.website-admin.content.legal.index', + 'can' => 'admin.website-admin.content.legal.view', + 'description' => 'Documentos como Términos, Aviso de Privacidad, etc.', + ], + ] + ], + 'Analítica y seguimiento' => [ + '_meta' => [ + 'icon' => 'ti ti-device-analytics', + 'description' => 'Conecta tu sitio con herramientas de análisis y tracking.', + 'widget_label' => 'Integraciones de analítica y seguimiento', + 'home_at_root' => true, + 'priority' => 300, + ], + 'submenu' => [ + 'Google Analytics' => [ + 'icon' => 'ti ti-chart-scatter-3d', + 'route' => 'admin.website-admin.analytics.google-analytics.index', + 'can' => 'admin.website-admin.analytics.google-analytics.view', + 'description' => 'Integra tu cuenta de Google Analytics.', + ], + 'Google Tags' => [ + 'icon' => 'ti ti-tags', + 'route' => 'admin.website-admin.analytics.google-tags.index', + 'can' => 'admin.website-admin.analytics.google-tags.view', + 'description' => 'Administra etiquetas de Google Tag Manager.', + ], + 'Google Search Console' => [ + 'icon' => 'ti ti-search', + 'route' => 'admin.website-admin.analytics.google-search-console.index', + 'can' => 'admin.website-admin.analytics.google-search-console.view', + 'description' => 'Verifica y gestiona tu sitio con Search Console.', + ], + 'Pixel Meta' => [ + 'icon' => 'ti ti-device-analytics', + 'route' => 'admin.website-admin.analytics.pixel-meta.index', + 'can' => 'admin.website-admin.analytics.pixel-meta.view', + 'description' => 'Integra el pixel de Meta (Facebook Ads).', + ], + ] + ], + 'Herramientas SEO' => [ + '_meta' => [ + 'icon' => 'ti ti-code-dots', + 'description' => 'Utilidades para mejorar la visibilidad en buscadores.', + 'widget_label' => 'Herramientas SEO y Metadatos', + 'home_at_root' => true, + 'priority' => 500, + ], + 'submenu' => [ + 'Mapa del sitio' => [ + 'icon' => 'ti ti-hierarchy', + 'route' => 'admin.website-admin.seo.sitemap.index', + 'can' => 'admin.website-admin.seo.sitemap.view', + 'description' => 'Genera y publica el sitemap.xml.', + ], + 'Google JSON-LD' => [ + 'icon' => 'ti ti-code-dots', + 'route' => 'admin.website-admin.seo.jsonld.index', + 'can' => 'admin.website-admin.seo.jsonld.view', + 'description' => 'Configura el esquema estructurado para Google.', + ], + 'Robots.txt' => [ + 'icon' => 'ti ti-code', + 'route' => 'admin.website-admin.seo.robots.index', + 'can' => 'admin.website-admin.seo.robots.view', + 'description' => 'Controla qué partes del sitio pueden ser indexadas.', + ], + 'manifest.json' => [ + 'icon' => 'ti ti-file-code', + 'route' => 'admin.website-admin.seo.manifest.index', + 'can' => 'admin.website-admin.seo.manifest.view', + 'description' => 'Configura la compatibilidad como PWA.', + ], + 'Canonical URLs' => [ + 'icon' => 'ti ti-link', + 'route' => 'admin.website-admin.seo.canonical.index', + 'can' => 'admin.website-admin.seo.canonical.view', + 'description' => 'Evita duplicidad de URLs con etiquetas canónicas.', + ], + 'Preview Social Cards' => [ + 'icon' => 'ti ti-brand-facebook', + 'route' => 'admin.website-admin.seo.social-cards.index', + 'can' => 'admin.website-admin.seo.social-cards.view', + 'description' => 'Define títulos, imágenes y previews sociales.', + ], + ] + ], + ] + ], + 'Blog' => [ + '_meta' => [ + 'icon' => 'ti ti-news', + 'description' => 'Publica, edita y organiza artículos, categorías y comentarios de tu blog.', + 'after_to' => 'Web & SEO', + ], + 'submenu' => [ + 'Categorias' => [ + 'icon' => 'ti ti-category', + 'route' => 'admin.website-admin.blog.categories.index', + 'can' => 'admin.website-admin.blog.categories.view', + ], + 'Etiquetas' => [ + 'icon' => 'ti ti-tags', + 'route' => 'admin.website-admin.blog.tags.index', + 'can' => 'admin.website-admin.blog.tags.view', + ], + 'Articulos' => [ + 'icon' => 'ti ti-news', + 'route' => 'admin.website-admin.blog.articles.index', + 'can' => 'admin.website-admin.blog.articles.view', + ], + 'Comentarios' => [ + 'icon' => 'ti ti-message', + 'route' => 'admin.website-admin.blog.comments.index', + 'can' => 'admin.website-admin.blog.comments.view', + ], + ] + ], + 'Plantillas' => [ + '_meta' => [ + 'icon' => 'ti ti-template', + 'description' => 'Gestiona las plantillas disponibles para tu sitio web.', + 'after_to' => 'Blog', + ], + 'submenu' => [ + 'Plantillas' => [ + 'icon' => 'ti ti-template', + 'route' => 'admin.website-admin.templates.index', + 'can' => 'admin.website-admin.templates.view', + ], + ] + ], +]; diff --git a/config/vuexy_website_admin_menu.php b/config/vuexy_website_admin_menu.php new file mode 100644 index 0000000..d00d9fe --- /dev/null +++ b/config/vuexy_website_admin_menu.php @@ -0,0 +1,218 @@ + [ + '_meta' => [ + 'icon' => 'ti ti-world', + 'description' => 'Administra múltiples sitios, dominios, plantillas y configuración global.', + 'widget_label' => 'Sitios Web y Dominios', + 'home_at_root' => true, + 'priority' => 100, + ], + 'submenu' => [ + 'Todos los sitios' => [ + 'icon' => 'ti ti-world', + 'route' => 'admin.website-admin.sites.index', + 'can' => 'admin.website-admin.sites.view', + 'description' => 'Listado general de sitios activos y configurables.', + ], + 'Crear nuevo sitio' => [ + 'icon' => 'ti ti-world-plus', + 'route' => 'admin.website-admin.sites.create', + 'can' => 'admin.website-admin.sites.create', + 'description' => 'Inicia un nuevo sitio web desde cero.', + ], + ], + ], + 'Configuración del Sitio' => [ + '_meta' => [ + 'icon' => 'ti ti-settings-cog', + 'description' => 'Ajustes, branding, visibilidad y plantilla del sitio actual.', + 'widget_label' => 'Configuración de Sitio Web', + 'home_at_root' => true, + 'priority' => 150, + ], + 'submenu' => [ + 'General y Branding' => [ + 'icon' => 'ti ti-tools', + 'route' => 'admin.website-admin.settings.general.index', + 'can' => 'admin.website-admin.settings.general.view', + 'description' => 'Nombre, idioma, favicon, y plantilla activa.', + ], + 'Indexación y Robots' => [ + 'icon' => 'ti ti-search', + 'route' => 'admin.website-admin.settings.indexing.index', + 'can' => 'admin.website-admin.settings.indexing.view', + 'description' => 'Controla el acceso de motores de búsqueda a tu sitio.', + ], + 'Canonical y manifest.json' => [ + 'icon' => 'ti ti-file-code', + 'route' => 'admin.website-admin.settings.canonical.index', + 'can' => 'admin.website-admin.settings.canonical.view', + 'description' => 'Evita duplicados y mejora la visibilidad PWA.', + ], + ], + ], + 'SEO y Metadatos' => [ + '_meta' => [ + 'icon' => 'ti ti-zoom-code', + 'description' => 'Herramientas de optimización SEO, metadatos y JSON-LD.', + 'widget_label' => 'SEO & Metadatos', + 'home_at_root' => true, + 'priority' => 200, + ], + 'submenu' => [ + 'Perfil SEO' => [ + 'icon' => 'ti ti-graph', + 'route' => 'admin.website-admin.seo.profile.index', + 'can' => 'admin.website-admin.seo.profile.view', + 'description' => 'Define metadatos, OG y Twitter Cards del sitio.', + ], + 'JSON-LD y Schema.org' => [ + 'icon' => 'ti ti-code-dots', + 'route' => 'admin.website-admin.seo.jsonld.index', + 'can' => 'admin.website-admin.seo.jsonld.view', + 'description' => 'Configura estructuras para Google y otros buscadores.', + ], + 'Mapa del sitio y Robots' => [ + 'icon' => 'ti ti-hierarchy', + 'route' => 'admin.website-admin.seo.sitemap.index', + 'can' => 'admin.website-admin.seo.sitemap.view', + 'description' => 'Controla el sitemap.xml y robots.txt globalmente.', + ], + ], + ], + 'CMS Koneko' => [ + '_meta' => [ + 'icon' => 'ti ti-layout-dashboard', + 'description' => 'Gestión de contenido visual, bloques, menús y plantillas.', + 'widget_label' => 'Editor de Contenido CMS', + 'home_at_root' => true, + 'priority' => 300, + ], + 'submenu' => [ + 'Páginas y bloques' => [ + 'icon' => 'ti ti-file-text', + 'route' => 'admin.website-admin.cms.contents.index', + 'can' => 'admin.website-admin.cms.contents.view', + 'description' => 'Administra contenido dinámico estructurado por bloques.', + ], + 'Menús del sitio' => [ + 'icon' => 'ti ti-hierarchy-3', + 'route' => 'admin.website-admin.cms.menus.index', + 'can' => 'admin.website-admin.cms.menus.view', + 'description' => 'Gestiona la navegación del sitio.', + ], + 'Versiones y previews' => [ + 'icon' => 'ti ti-clock-edit', + 'route' => 'admin.website-admin.cms.versions.index', + 'can' => 'admin.website-admin.cms.versions.view', + 'description' => 'Historial de versiones de contenido y vista previa.', + ], + 'Plantillas' => [ + 'icon' => 'ti ti-template', + 'route' => 'admin.website-admin.cms.templates.index', + 'can' => 'admin.website-admin.cms.templates.view', + 'description' => 'Gestiona y asigna plantillas de presentación.', + ], + ], + ], + 'Integraciones API' => [ + '_meta' => [ + 'icon' => 'ti ti-plug', + 'description' => 'Configura APIs y servicios externos como Analytics o Chat.', + 'widget_label' => 'Extensiones y APIs', + 'home_at_root' => true, + 'priority' => 400, + ], + 'submenu' => [ + 'Google & Meta' => [ + 'icon' => 'ti ti-brand-google', + 'route' => 'admin.website-admin.integrations.analytics.index', + 'can' => 'admin.website-admin.integrations.analytics.view', + 'description' => 'Google Analytics, Search Console, Pixel Meta, etc.', + ], + 'Chat y comunicación' => [ + 'icon' => 'ti ti-message-dots', + 'route' => 'admin.website-admin.integrations.chat.index', + 'can' => 'admin.website-admin.integrations.chat.view', + 'description' => 'Messenger, WhatsApp, Tawk.to y más.', + ], + 'Traducción e idioma' => [ + 'icon' => 'ti ti-language', + 'route' => 'admin.website-admin.integrations.translate.index', + 'can' => 'admin.website-admin.integrations.translate.view', + 'description' => 'Integraciones como Google Translate o DeepL.', + ], + ], + ], + 'Sistema de Cache' => [ + '_meta' => [ + 'icon' => 'ti ti-database-cog', + 'description' => 'Herramientas avanzadas de rendimiento y renderizado.', + 'widget_label' => 'Motor de Cache', + 'home_at_root' => true, + 'priority' => 500, + ], + 'submenu' => [ + 'HTML completo' => [ + 'icon' => 'ti ti-file-type-html', + 'route' => 'admin.website-admin.cache.full.index', + 'can' => 'admin.website-admin.cache.full.view', + 'description' => 'Gestiona caché render de páginas completas.', + ], + 'Bloques de contenido' => [ + 'icon' => 'ti ti-box-model', + 'route' => 'admin.website-admin.cache.blocks.index', + 'can' => 'admin.website-admin.cache.blocks.view', + 'description' => 'Visualiza y limpia caché por bloques individuales.', + ], + 'Previews firmadas' => [ + 'icon' => 'ti ti-key', + 'route' => 'admin.website-admin.cache.previews.index', + 'can' => 'admin.website-admin.cache.previews.view', + 'description' => 'Controla la vigencia y firma de URLs temporales.', + ], + ], + ], + 'Blog' => [ + '_meta' => [ + 'icon' => 'ti ti-news', + 'description' => 'Publica, edita y organiza artículos, categorías y comentarios de tu blog.', + 'after_to' => 'Web & SEO', + ], + 'submenu' => [ + 'Categorias' => [ + 'icon' => 'ti ti-category', + 'route' => 'admin.website-admin.blog.categories.index', + 'can' => 'admin.website-admin.blog.categories.view', + ], + 'Etiquetas' => [ + 'icon' => 'ti ti-tags', + 'route' => 'admin.website-admin.blog.tags.index', + 'can' => 'admin.website-admin.blog.tags.view', + ], + 'Articulos' => [ + 'icon' => 'ti ti-news', + 'route' => 'admin.website-admin.blog.articles.index', + 'can' => 'admin.website-admin.blog.articles.view', + ], + 'Comentarios' => [ + 'icon' => 'ti ti-message', + 'route' => 'admin.website-admin.blog.comments.index', + 'can' => 'admin.website-admin.blog.comments.view', + ], + ] + ], + 'Plantillas' => [ + '_meta' => [ + 'icon' => 'ti ti-template', + 'description' => 'Gestiona las plantillas disponibles para tu sitio web.', + 'after_to' => 'Blog', + ] + ], +]; diff --git a/database/data/rbac/permissions.json b/database/data/rbac/permissions.json new file mode 100644 index 0000000..adda4b5 --- /dev/null +++ b/database/data/rbac/permissions.json @@ -0,0 +1,1154 @@ +{ + "module": "admin.website-admin", + "name": { + "es": "Administrador de Sitio Web", + "en": "Website Administrator" + }, + "_meta": { + "description": { + "es": "Permisos para la gestión del sitio web", + "en": "Permissions for managing the website" + }, + "icon": "ti ti-settings" + }, + "priority": 200, + "groups": { + "website-settings": { + "name": { + "es": "Configuración del Sitio Web", + "en": "Website Settings" + }, + "_meta": { + "description": { + "es": "Permisos para ajustes generales y SEO del sitio web", + "en": "Permissions for general settings and website SEO" + }, + "icon": "ti ti-settings" + }, + "priority": 100, + "sub_groups": { + "general": { + "name": { + "es": "Ajustes generales", + "en": "General settings" + }, + "_meta": { + "description": { + "es": "Personalización general del sitio web", + "en": "General customization of the website" + }, + "icon": "ti ti-settings" + }, + "priority": 100, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración general del sitio", + "en": "View general site settings" + }, + "key": "settings.general.view" + } + ] + }, + "social": { + "name": { + "es": "Redes sociales", + "en": "Social media" + }, + "_meta": { + "description": { + "es": "Gestión de enlaces a redes sociales", + "en": "Social media link management" + }, + "icon": "ti ti-share" + }, + "priority": 200, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver enlaces sociales", + "en": "View social links" + }, + "key": "settings.social.view" + } + ] + }, + "indexing": { + "name": { + "es": "Indexación", + "en": "Indexing" + }, + "_meta": { + "description": { + "es": "Control de visibilidad en buscadores", + "en": "Control site visibility in search engines" + }, + "icon": "ti ti-search" + }, + "priority": 300, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de indexado", + "en": "View indexing settings" + }, + "key": "settings.indexing.view" + } + ] + }, + "info": { + "name": { + "es": "Información de contacto", + "en": "Contact information" + }, + "_meta": { + "description": { + "es": "Datos de contacto y localización", + "en": "Contact data and location" + }, + "icon": "ti ti-device-mobile-message" + }, + "priority": 400, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver información de contacto", + "en": "View contact information" + }, + "key": "contact.info.view" + } + ] + }, + "form": { + "name": { + "es": "Formulario de contacto", + "en": "Contact form" + }, + "_meta": { + "description": { + "es": "Gestión del formulario de contacto", + "en": "Contact form configuration" + }, + "icon": "ti ti-mail-cog" + }, + "priority": 500, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver formulario de contacto", + "en": "View contact form" + }, + "key": "contact.form.view" + } + ] + }, + "messenger": { + "name": { + "es": "Facebook Messenger", + "en": "Facebook Messenger" + }, + "_meta": { + "description": { + "es": "Activa el chat de Messenger en tu sitio.", + "en": "Activate Messenger chat on your site." + }, + "icon": "ti ti-brand-messenger" + }, + "priority": 600, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Messenger", + "en": "View Messenger configuration" + }, + "key": "comunication.messenger.view" + } + ] + }, + "whatsapp": { + "name": { + "es": "WhatsApp Chat", + "en": "WhatsApp Chat" + }, + "_meta": { + "description": { + "es": "Integra un botón de chat directo con WhatsApp.", + "en": "Integrate a WhatsApp direct chat button." + }, + "icon": "ti ti-brand-whatsapp" + }, + "priority": 700, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de WhatsApp", + "en": "View WhatsApp configuration" + }, + "key": "comunication.whatsapp.view" + } + ] + }, + "tawk-to": { + "name": { + "es": "Tawk.to", + "en": "Tawk.to" + }, + "_meta": { + "description": { + "es": "Agrega soporte en vivo con Tawk.to.", + "en": "Add live support with Tawk.to." + }, + "icon": "ti ti-message-dots" + }, + "priority": 800, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Tawk.to", + "en": "View Tawk.to configuration" + }, + "key": "comunication.tawk-to.view" + } + ] + }, + "twitter": { + "name": { + "es": "Twitter API", + "en": "Twitter API" + }, + "_meta": { + "description": { + "es": "Configura la integración con Twitter/X.", + "en": "Configure integration with Twitter/X." + }, + "icon": "ti ti-brand-x" + }, + "priority": 900, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Twitter", + "en": "View Twitter configuration" + }, + "key": "comunication.twitter.view" + } + ] + }, + "google-translate": { + "name": { + "es": "Google Translate", + "en": "Google Translate" + }, + "_meta": { + "description": { + "es": "Activa la traducción automática con Google Translate.", + "en": "Enable automatic translation using Google Translate." + }, + "icon": "ti ti-language" + }, + "priority": 1000, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Google Translate", + "en": "View Google Translate settings" + }, + "key": "translate.google-translate.view" + }, + { + "action": "update", + "label": { + "es": "Editar configuración de Google Translate", + "en": "Update Google Translate settings" + }, + "key": "translate.google-translate.update" + } + ] + } + } + }, + "website-cms": { + "name": { + "es": "Gestor de Contenidos", + "en": "Content Manager" + }, + "_meta": { + "description": { + "es": "Administrador de contenidos dinámicos del sitio web.", + "en": "Administrator of dynamic website content." + }, + "icon": "ti ti-layout-grid-add" + }, + "priority": 200, + "sub_groups": { + "menus": { + "name": { + "es": "Menús del sitio", + "en": "Site Menus" + }, + "_meta": { + "description": { + "es": "Estructura de navegación y menús.", + "en": "Navigation structure and menus." + }, + "icon": "ti ti-hierarchy-3" + }, + "priority": 100, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver menús del sitio", + "en": "View site menus" + }, + "key": "cms.menus.view" + }, + { + "action": "update", + "label": { + "es": "Editar menús del sitio", + "en": "Edit site menus" + }, + "key": "cms.menus.update" + } + ] + }, + "seo": { + "name": { + "es": "Perfil SEO", + "en": "SEO Profile" + }, + "_meta": { + "description": { + "es": "Configuración de parámetros SEO.", + "en": "SEO parameter configuration." + }, + "icon": "ti ti-settings" + }, + "priority": 200, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver perfil SEO", + "en": "View SEO profile" + }, + "key": "cms.seo.view" + }, + { + "action": "update", + "label": { + "es": "Editar perfil SEO", + "en": "Edit SEO profile" + }, + "key": "cms.seo.update" + } + ] + }, + "contents": { + "name": { + "es": "Contenidos dinámicos", + "en": "Dynamic Contents" + }, + "_meta": { + "description": { + "es": "Páginas, bloques y parciales Blade.", + "en": "Pages, blocks, and Blade partials." + }, + "icon": "ti ti-file-text" + }, + "priority": 300, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver contenidos", + "en": "View contents" + }, + "key": "cms.contents.view" + }, + { + "action": "create", + "label": { + "es": "Crear contenido", + "en": "Create content" + }, + "key": "cms.contents.create" + }, + { + "action": "update", + "label": { + "es": "Editar contenido", + "en": "Edit content" + }, + "key": "cms.contents.update" + }, + { + "action": "delete", + "label": { + "es": "Eliminar contenido", + "en": "Delete content" + }, + "key": "cms.contents.delete" + } + ] + }, + "versions": { + "name": { + "es": "Versiones de contenido", + "en": "Content Versions" + }, + "_meta": { + "description": { + "es": "Gestión de versiones y restauraciones.", + "en": "Version and restoration management." + }, + "icon": "ti ti-clock-edit" + }, + "priority": 400, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver versiones de contenido", + "en": "View content versions" + }, + "key": "cms.versions.view" + }, + { + "action": "reopen", + "label": { + "es": "Restaurar versión", + "en": "Restore version" + }, + "key": "cms.versions.reopen" + } + ] + }, + "templates": { + "name": { + "es": "Plantillas disponibles", + "en": "Available Templates" + }, + "_meta": { + "description": { + "es": "Plantillas base y organización.", + "en": "Base templates and organization." + }, + "icon": "ti ti-template" + }, + "priority": 500, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver plantillas", + "en": "View templates" + }, + "key": "cms.templates.view" + }, + { + "action": "update", + "label": { + "es": "Editar plantillas", + "en": "Edit templates" + }, + "key": "cms.templates.update" + } + ] + }, + "cache": { + "name": { + "es": "Caché HTML renderizado", + "en": "Rendered HTML Cache" + }, + "_meta": { + "description": { + "es": "Visualiza y limpia la caché de HTML completo del sitio web.", + "en": "View and clear full-page HTML cache for the website." + }, + "icon": "ti ti-file-type-html" + }, + "priority": 600, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver caché HTML", + "en": "View HTML Cache" + }, + "key": "website.cache.fullpage.view" + }, + { + "action": "clean", + "label": { + "es": "Limpiar caché HTML", + "en": "Clear HTML Cache" + }, + "key": "website.cache.fullpage.clean" + } + ] + }, + "signed-previews": { + "name": { + "es": "Previsualizaciones firmadas", + "en": "Signed Previews" + }, + "_meta": { + "description": { + "es": "URLs de vista previa y control de firma temporal.", + "en": "Preview URLs and temporary signature control." + }, + "icon": "ti ti-lock-access" + }, + "priority": 700, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver previsualizaciones firmadas", + "en": "View signed previews" + }, + "key": "website.cache.signed-previews.view" + }, + { + "action": "clean", + "label": { + "es": "Limpiar previsualizaciones firmadas", + "en": "Clear signed previews" + }, + "key": "website.cache.signed-previews.clean" + } + ] + } + } + }, + "website-content": { + "name": { + "es": "Contenido", + "en": "Content" + }, + "_meta": { + "description": { + "es": "Maneja contenido informativo y visual.", + "en": "Manage informational and visual content." + }, + "icon": "ti ti-hierarchy" + }, + "priority": 300, + "sub_groups": { + "faq": { + "name": { + "es": "Preguntas frecuentes", + "en": "Frequently Asked Questions" + }, + "_meta": { + "description": { + "es": "Administra las preguntas frecuentes del sitio.", + "en": "Manage the site's FAQ content." + }, + "icon": "ti ti-bubble-text" + }, + "priority": 100, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver preguntas frecuentes", + "en": "View FAQ" + }, + "key": "content.faq.view" + }, + { + "action": "create", + "label": { + "es": "Crear preguntas frecuentes", + "en": "Create FAQ" + }, + "key": "content.faq.create" + }, + { + "action": "update", + "label": { + "es": "Editar preguntas frecuentes", + "en": "Edit FAQ" + }, + "key": "content.faq.update" + }, + { + "action": "delete", + "label": { + "es": "Eliminar preguntas frecuentes", + "en": "Delete FAQ" + }, + "key": "content.faq.delete" + } + ] + }, + "gallery": { + "name": { + "es": "Galería de imágenes", + "en": "Image Gallery" + }, + "_meta": { + "description": { + "es": "Agrega y organiza tus imágenes.", + "en": "Add and organize your images." + }, + "icon": "ti ti-photo" + }, + "priority": 200, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver galería de imágenes", + "en": "View image gallery" + }, + "key": "content.gallery.view" + }, + { + "action": "create", + "label": { + "es": "Subir imagen", + "en": "Upload image" + }, + "key": "content.gallery.create" + }, + { + "action": "delete", + "label": { + "es": "Eliminar imagen", + "en": "Delete image" + }, + "key": "content.gallery.delete" + } + ] + }, + "legal": { + "name": { + "es": "Avisos legales", + "en": "Legal Notices" + }, + "_meta": { + "description": { + "es": "Documentos como Términos, Aviso de Privacidad, etc.", + "en": "Documents such as Terms of Service, Privacy Policy, etc." + }, + "icon": "ti ti-file-text-shield" + }, + "priority": 300, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver avisos legales", + "en": "View legal notices" + }, + "key": "content.legal.view" + }, + { + "action": "update", + "label": { + "es": "Editar avisos legales", + "en": "Edit legal notices" + }, + "key": "content.legal.update" + } + ] + } + } + }, + "website-analytics": { + "name": { + "es": "Analítica y seguimiento", + "en": "Analytics and Tracking" + }, + "_meta": { + "description": { + "es": "Permisos para gestionar integraciones de analítica y seguimiento.", + "en": "Permissions to manage analytics and tracking integrations." + }, + "icon": "ti ti-device-analytics" + }, + "priority": 400, + "sub_groups": { + "google-analytics": { + "name": { + "es": "Google Analytics", + "en": "Google Analytics" + }, + "_meta": { + "description": { + "es": "Integra tu cuenta de Google Analytics.", + "en": "Integrate your Google Analytics account." + }, + "icon": "ti ti-chart-scatter-3d" + }, + "priority": 100, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Google Analytics", + "en": "View Google Analytics configuration" + }, + "key": "analytics.google-analytics.view" + } + ] + }, + "google-tags": { + "name": { + "es": "Google Tags", + "en": "Google Tags" + }, + "_meta": { + "description": { + "es": "Administra etiquetas de Google Tag Manager.", + "en": "Manage Google Tag Manager tags." + }, + "icon": "ti ti-tags" + }, + "priority": 200, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Google Tags", + "en": "View Google Tags configuration" + }, + "key": "analytics.google-tags.view" + } + ] + }, + "google-search-console": { + "name": { + "es": "Google Search Console", + "en": "Google Search Console" + }, + "_meta": { + "description": { + "es": "Verifica y gestiona tu sitio con Search Console.", + "en": "Verify and manage your site with Search Console." + }, + "icon": "ti ti-search" + }, + "priority": 300, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Search Console", + "en": "View Search Console configuration" + }, + "key": "analytics.google-search-console.view" + } + ] + }, + "pixel-meta": { + "name": { + "es": "Pixel Meta", + "en": "Meta Pixel" + }, + "_meta": { + "description": { + "es": "Integra el pixel de Meta (Facebook Ads).", + "en": "Integrate Meta Pixel (Facebook Ads)." + }, + "icon": "ti ti-device-analytics" + }, + "priority": 400, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Meta Pixel", + "en": "View Meta Pixel configuration" + }, + "key": "analytics.pixel-meta.view" + } + ] + } + } + }, + "website-seo-tools": { + "name": { + "es": "Herramientas SEO", + "en": "SEO Tools" + }, + "_meta": { + "description": { + "es": "Permisos para gestionar herramientas y metadatos SEO.", + "en": "Permissions to manage SEO tools and metadata." + }, + "icon": "ti ti-code-dots" + }, + "priority": 500, + "sub_groups": { + "sitemap": { + "name": { + "es": "Mapa del sitio", + "en": "Sitemap" + }, + "_meta": { + "description": { + "es": "Genera y publica el sitemap.xml.", + "en": "Generate and publish sitemap.xml." + }, + "icon": "ti ti-hierarchy" + }, + "priority": 100, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Sitemap", + "en": "View Sitemap configuration" + }, + "key": "seo-tools.sitemap.view" + } + ] + }, + "jsonld": { + "name": { + "es": "Google JSON-LD", + "en": "Google JSON-LD" + }, + "_meta": { + "description": { + "es": "Configura el esquema estructurado para Google.", + "en": "Configure structured schema for Google." + }, + "icon": "ti ti-code-dots" + }, + "priority": 200, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de JSON-LD", + "en": "View JSON-LD configuration" + }, + "key": "seo-tools.jsonld.view" + } + ] + }, + "robots": { + "name": { + "es": "Robots.txt", + "en": "Robots.txt" + }, + "_meta": { + "description": { + "es": "Controla qué partes del sitio pueden ser indexadas.", + "en": "Control which parts of the site can be indexed." + }, + "icon": "ti ti-code" + }, + "priority": 300, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Robots.txt", + "en": "View Robots.txt configuration" + }, + "key": "seo-tools.robots.view" + } + ] + }, + "manifest": { + "name": { + "es": "manifest.json", + "en": "manifest.json" + }, + "_meta": { + "description": { + "es": "Configura la compatibilidad como PWA.", + "en": "Configure PWA compatibility." + }, + "icon": "ti ti-file-code" + }, + "priority": 400, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de manifest.json", + "en": "View manifest.json configuration" + }, + "key": "seo-tools.manifest.view" + } + ] + }, + "canonical": { + "name": { + "es": "Canonical URLs", + "en": "Canonical URLs" + }, + "_meta": { + "description": { + "es": "Evita duplicidad de URLs con etiquetas canónicas.", + "en": "Avoid URL duplication with canonical tags." + }, + "icon": "ti ti-link" + }, + "priority": 500, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Canonical", + "en": "View Canonical configuration" + }, + "key": "seo-tools.canonical.view" + } + ] + }, + "social-cards": { + "name": { + "es": "Preview Social Cards", + "en": "Preview Social Cards" + }, + "_meta": { + "description": { + "es": "Define títulos, imágenes y previews sociales.", + "en": "Define titles, images and social previews." + }, + "icon": "ti ti-brand-facebook" + }, + "priority": 600, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver configuración de Social Cards", + "en": "View Social Cards configuration" + }, + "key": "seo-tools.social-cards.view" + } + ] + } + } + }, + "website-blog": { + "name": { + "es": "Blog", + "en": "Blog" + }, + "_meta": { + "description": { + "es": "Publica, edita y organiza artículos, categorías y comentarios de tu blog.", + "en": "Publish, edit, and organize blog articles, categories, and comments." + }, + "icon": "ti ti-news", + "after_to": "Sitio web" + }, + "priority": 600, + "sub_groups": { + "categories": { + "name": { + "es": "Categorías", + "en": "Categories" + }, + "_meta": { + "icon": "ti ti-category" + }, + "priority": 100, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver categorías", + "en": "View categories" + }, + "key": "blog.categories.view" + }, + { + "action": "create", + "label": { + "es": "Crear categoría", + "en": "Create category" + }, + "key": "blog.categories.create" + }, + { + "action": "update", + "label": { + "es": "Editar categoría", + "en": "Edit category" + }, + "key": "blog.categories.update" + }, + { + "action": "delete", + "label": { + "es": "Eliminar categoría", + "en": "Delete category" + }, + "key": "blog.categories.delete" + } + ] + }, + "tags": { + "name": { + "es": "Etiquetas", + "en": "Tags" + }, + "_meta": { + "icon": "ti ti-tags" + }, + "priority": 200, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver etiquetas", + "en": "View tags" + }, + "key": "blog.tags.view" + }, + { + "action": "create", + "label": { + "es": "Crear etiqueta", + "en": "Create tag" + }, + "key": "blog.tags.create" + }, + { + "action": "update", + "label": { + "es": "Editar etiqueta", + "en": "Edit tag" + }, + "key": "blog.tags.update" + }, + { + "action": "delete", + "label": { + "es": "Eliminar etiqueta", + "en": "Delete tag" + }, + "key": "blog.tags.delete" + } + ] + }, + "articles": { + "name": { + "es": "Artículos", + "en": "Articles" + }, + "_meta": { + "icon": "ti ti-news" + }, + "priority": 300, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver artículos", + "en": "View articles" + }, + "key": "blog.articles.view" + }, + { + "action": "create", + "label": { + "es": "Crear artículo", + "en": "Create article" + }, + "key": "blog.articles.create" + }, + { + "action": "update", + "label": { + "es": "Editar artículo", + "en": "Edit article" + }, + "key": "blog.articles.update" + }, + { + "action": "delete", + "label": { + "es": "Eliminar artículo", + "en": "Delete article" + }, + "key": "blog.articles.delete" + }, + { + "action": "publish", + "label": { + "es": "Publicar artículo", + "en": "Publish article" + }, + "key": "blog.articles.publish" + }, + { + "action": "archive", + "label": { + "es": "Archivar artículo", + "en": "Archive article" + }, + "key": "blog.articles.archive" + } + ] + }, + "comments": { + "name": { + "es": "Comentarios", + "en": "Comments" + }, + "_meta": { + "icon": "ti ti-message" + }, + "priority": 400, + "permissions": [ + { + "action": "view", + "label": { + "es": "Ver comentarios", + "en": "View comments" + }, + "key": "blog.comments.view" + }, + { + "action": "delete", + "label": { + "es": "Eliminar comentario", + "en": "Delete comment" + }, + "key": "blog.comments.delete" + }, + { + "action": "approve", + "label": { + "es": "Aprobar comentario", + "en": "Approve comment" + }, + "key": "blog.comments.approve" + }, + { + "action": "cancel", + "label": { + "es": "Rechazar comentario", + "en": "Reject comment" + }, + "key": "blog.comments.cancel" + } + ] + } + } + } + } +} diff --git a/database/data/rbac/roles.json b/database/data/rbac/roles.json new file mode 100644 index 0000000..416014e --- /dev/null +++ b/database/data/rbac/roles.json @@ -0,0 +1,280 @@ +{ + "SuperAdmin" : { + "permissions" : [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.form.view", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.translate.google-translate.view", + "admin.website-admin.translate.google-translate.update", + "admin.website-admin.cms.menus.view", + "admin.website-admin.cms.menus.update", + "admin.website-admin.cms.seo.view", + "admin.website-admin.cms.seo.update", + "admin.website-admin.cms.contents.view", + "admin.website-admin.cms.contents.create", + "admin.website-admin.cms.contents.update", + "admin.website-admin.cms.contents.delete", + "admin.website-admin.cms.versions.view", + "admin.website-admin.cms.versions.reopen", + "admin.website-admin.cms.templates.view", + "admin.website-admin.cms.templates.update", + "admin.website-admin.website.cache.fullpage.view", + "admin.website-admin.website.cache.fullpage.clean", + "admin.website-admin.website.cache.signed-previews.view", + "admin.website-admin.website.cache.signed-previews.clean", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.faq.create", + "admin.website-admin.content.faq.update", + "admin.website-admin.content.faq.delete", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.gallery.create", + "admin.website-admin.content.gallery.delete", + "admin.website-admin.content.legal.view", + "admin.website-admin.content.legal.update", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.seo-tools.sitemap.view", + "admin.website-admin.seo-tools.jsonld.view", + "admin.website-admin.seo-tools.robots.view", + "admin.website-admin.seo-tools.manifest.view", + "admin.website-admin.seo-tools.canonical.view", + "admin.website-admin.seo-tools.social-cards.view", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.categories.create", + "admin.website-admin.blog.categories.update", + "admin.website-admin.blog.categories.delete", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.tags.create", + "admin.website-admin.blog.tags.update", + "admin.website-admin.blog.tags.delete", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.articles.create", + "admin.website-admin.blog.articles.update", + "admin.website-admin.blog.articles.delete", + "admin.website-admin.blog.articles.publish", + "admin.website-admin.blog.articles.archive", + "admin.website-admin.blog.comments.view", + "admin.website-admin.blog.comments.delete", + "admin.website-admin.blog.comments.approve", + "admin.website-admin.blog.comments.cancel" + ] + }, + "WebsiteAdmin": { + "_meta": { + "description": { + "es": "Gestiona contenidos, menús, SEO y configuración del sitio web.", + "en": "Manages contents, menus, SEO, and website settings." + }, + "icon": "ti ti-world", + "style": "warning" + }, + "permissions" : [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.form.view", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.translate.google-translate.view", + "admin.website-admin.translate.google-translate.update", + "admin.website-admin.cms.menus.view", + "admin.website-admin.cms.menus.update", + "admin.website-admin.cms.seo.view", + "admin.website-admin.cms.seo.update", + "admin.website-admin.cms.contents.view", + "admin.website-admin.cms.contents.create", + "admin.website-admin.cms.contents.update", + "admin.website-admin.cms.contents.delete", + "admin.website-admin.cms.versions.view", + "admin.website-admin.cms.versions.reopen", + "admin.website-admin.cms.templates.view", + "admin.website-admin.cms.templates.update", + "admin.website-admin.website.cache.fullpage.view", + "admin.website-admin.website.cache.fullpage.clean", + "admin.website-admin.website.cache.signed-previews.view", + "admin.website-admin.website.cache.signed-previews.clean", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.faq.create", + "admin.website-admin.content.faq.update", + "admin.website-admin.content.faq.delete", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.gallery.create", + "admin.website-admin.content.gallery.delete", + "admin.website-admin.content.legal.view", + "admin.website-admin.content.legal.update", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.seo-tools.sitemap.view", + "admin.website-admin.seo-tools.jsonld.view", + "admin.website-admin.seo-tools.robots.view", + "admin.website-admin.seo-tools.manifest.view", + "admin.website-admin.seo-tools.canonical.view", + "admin.website-admin.seo-tools.social-cards.view", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.categories.create", + "admin.website-admin.blog.categories.update", + "admin.website-admin.blog.categories.delete", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.tags.create", + "admin.website-admin.blog.tags.update", + "admin.website-admin.blog.tags.delete", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.articles.create", + "admin.website-admin.blog.articles.update", + "admin.website-admin.blog.articles.delete", + "admin.website-admin.blog.articles.publish", + "admin.website-admin.blog.articles.archive", + "admin.website-admin.blog.comments.view", + "admin.website-admin.blog.comments.delete", + "admin.website-admin.blog.comments.approve", + "admin.website-admin.blog.comments.cancel" + ] + }, + "WebsiteContentEditor": { + "_meta": { + "description": { + "es": "Puede crear, editar y publicar contenidos y artículos del blog.", + "en": "Can create, edit, and publish content and blog posts." + }, + "icon": "ti ti-article", + "style": "info" + }, + "permissions": [ + "admin.website-admin.cms.menus.view", + "admin.website-admin.cms.menus.update", + "admin.website-admin.cms.seo.view", + "admin.website-admin.cms.seo.update", + "admin.website-admin.cms.contents.view", + "admin.website-admin.cms.contents.create", + "admin.website-admin.cms.contents.update", + "admin.website-admin.cms.contents.delete", + "admin.website-admin.cms.versions.view", + "admin.website-admin.cms.templates.view", + "admin.website-admin.website.cache.fullpage.view", + "admin.website-admin.website.cache.fullpage.clean", + "admin.website-admin.website.cache.signed-previews.view", + "admin.website-admin.website.cache.signed-previews.clean", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.faq.create", + "admin.website-admin.content.faq.update", + "admin.website-admin.content.faq.delete", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.gallery.create", + "admin.website-admin.content.gallery.delete", + "admin.website-admin.content.legal.view", + "admin.website-admin.content.legal.update", + "admin.website-admin.seo-tools.sitemap.view", + "admin.website-admin.seo-tools.jsonld.view", + "admin.website-admin.seo-tools.robots.view", + "admin.website-admin.seo-tools.manifest.view", + "admin.website-admin.seo-tools.canonical.view", + "admin.website-admin.seo-tools.social-cards.view" ] + }, + "BlogEditor": { + "_meta": { + "description": { + "es": "Puede crear, editar y publicar contenidos y artículos del blog.", + "en": "Can create, edit, and publish content and blog posts." + }, + "icon": "ti ti-article", + "style": "info" + }, + "permissions": [ + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.categories.create", + "admin.website-admin.blog.categories.update", + "admin.website-admin.blog.categories.delete", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.tags.create", + "admin.website-admin.blog.tags.update", + "admin.website-admin.blog.tags.delete", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.articles.create", + "admin.website-admin.blog.articles.update", + "admin.website-admin.blog.articles.delete", + "admin.website-admin.blog.articles.publish", + "admin.website-admin.blog.articles.archive", + "admin.website-admin.blog.comments.view", + "admin.website-admin.blog.comments.delete", + "admin.website-admin.blog.comments.approve", + "admin.website-admin.blog.comments.cancel" + ] + + }, + "WebsiteSEO": { + "_meta": { + "description": { + "es": "Gestiona herramientas SEO, Sitemap, Robots y JSON-LD.", + "en": "Manages SEO tools, Sitemap, Robots, and JSON-LD." + }, + "icon": "ti ti-chart-arcs", + "style": "warning" + }, + "permissions": [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.seo-tools.sitemap.view", + "admin.website-admin.seo-tools.jsonld.view", + "admin.website-admin.seo-tools.robots.view", + "admin.website-admin.seo-tools.manifest.view", + "admin.website-admin.seo-tools.canonical.view", + "admin.website-admin.seo-tools.social-cards.view" + ] + + }, + "Auditor" : { + "permissions" : [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.form.view", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.translate.google-translate.view", + "admin.website-admin.cms.menus.view", + "admin.website-admin.cms.seo.view", + "admin.website-admin.cms.contents.view", + "admin.website-admin.cms.versions.view", + "admin.website-admin.cms.templates.view", + "admin.website-admin.website.cache.fullpage.view", + "admin.website-admin.website.cache.signed-previews.view", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.legal.view", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.seo-tools.sitemap.view", + "admin.website-admin.seo-tools.jsonld.view", + "admin.website-admin.seo-tools.robots.view", + "admin.website-admin.seo-tools.manifest.view", + "admin.website-admin.seo-tools.canonical.view", + "admin.website-admin.seo-tools.social-cards.view", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.comments.view" + ] + } +} diff --git a/database/data/website-admin/website_agroform_contents.json b/database/data/website-admin/website_agroform_contents.json new file mode 100644 index 0000000..b2ced92 --- /dev/null +++ b/database/data/website-admin/website_agroform_contents.json @@ -0,0 +1,192 @@ +[ + { + "site_id": 1, + "title": "Agroform", + "slug": "", + "description": "Bienvenido a agroform, soluciones tecnológicas para empresas.", + "keywords": ["agroform", "erp", "tecnología", "soluciones"], + "template": "home", + "type": "page", + "is_partial": false, + "seo_profile_id": "inicio-agroform", + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx", + "content_blocks": [ + {"type": "hero", "title": "Soluciones Tecnológicas para tu Empresa", "subtitle": "Desde ERP hasta eCommerce", "image": "/images/hero.jpg"}, + {"type": "features", "items": [{"icon": "ti ti-server", "label": "Infraestructura"}, {"icon": "ti ti-code", "label": "Desarrollo a medida"}]}, + {"type": "cta", "text": "Solicita una demo sin costo", "button": "Agendar llamada"} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Nosotros", + "slug": "nosotros", + "description": "Conoce la historia, visión y equipo de Koneko.", + "keywords": ["nosotros", "equipo", "historia", "koneko"], + "template": "default", + "type": "page", + "is_partial": false, + "seo_profile_id": "inicio-agroform", + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx/nosotros", + "content_blocks": [ + {"type": "text", "heading": "¿Quiénes somos?", "content": "Somos una empresa de tecnología con sede en México..."}, + {"type": "team", "members": [{"name": "Arturo", "role": "Director Técnico"}, {"name": "Laura", "role": "Marketing"}]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Servicios", + "slug": "servicios", + "description": "Explora nuestros servicios: ERP, eCommerce, hosting y más.", + "keywords": ["erp", "servicios", "agroform", "sistemas"], + "template": "services", + "type": "page", + "is_partial": false, + "seo_profile_id": "servicios-agroform", + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx/servicios", + "content_blocks": [ + {"type": "service_list", "items": [ + {"title": "ERP Agroform", "description": "Sistema de gestión empresarial completo"}, + {"title": "Tienda Virtual", "description": "Ecommerce 100% administrable con SEO nativo"}, + {"title": "Infraestructura", "description": "Servidores, VPS, Proxmox y más"} + ]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Contacto", + "slug": "contacto", + "description": "Contáctanos para resolver tus dudas o agendar una demo.", + "keywords": ["contacto", "soporte", "demo", "ayuda"], + "template": "contact", + "type": "page", + "is_partial": false, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx/contacto", + "content_blocks": [ + {"type": "form", "fields": ["nombre", "email", "mensaje"], "submit_label": "Enviar mensaje"}, + {"type": "map", "lat": 19.4326, "lng": -99.1332} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Aviso de Privacidad", + "slug": "aviso-privacidad", + "description": "Consulta nuestro aviso de privacidad actualizado.", + "keywords": ["legal", "privacidad", "datos personales"], + "template": "legal", + "type": "page", + "is_partial": false, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx/aviso-privacidad", + "content_blocks": [ + {"type": "text", "heading": "Política de privacidad", "content": "Tus datos son importantes para nosotros..."} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Preguntas Frecuentes", + "slug": "faq", + "description": "Resuelve tus dudas rápidamente con nuestras respuestas frecuentes.", + "keywords": ["faq", "dudas", "soporte"], + "template": "faq", + "type": "page", + "is_partial": true, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "faq", "items": [ + {"q": "¿Tienen soporte técnico?", "a": "Sí, disponible 24/7 para clientes activos."}, + {"q": "¿Qué formas de pago aceptan?", "a": "Transferencia, tarjeta y criptomonedas."} + ]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Header Principal", + "slug": "header-principal", + "description": "Encabezado superior con navegación.", + "keywords": [], + "template": null, + "type": "partial", + "is_partial": true, + "seo_profile_id": 1, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "menu", "menu_slug": "principal"} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Footer General", + "slug": "footer-general", + "description": "Pie de página con enlaces y datos legales.", + "keywords": [], + "template": null, + "type": "partial", + "is_partial": true, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "footer", "links": ["inicio", "servicios", "contacto"]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Categoría: Blog", + "slug": "blog", + "description": "Explora nuestros artículos de tecnología y negocios.", + "keywords": ["blog", "noticias", "tips", "artículos"], + "template": "blog_index", + "type": "category", + "is_partial": false, + "seo_profile_id": "blog", + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx/blog", + "content_blocks": [], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 1, + "title": "Artículo: Ventajas del ERP", + "slug": "blog/ventajas-del-erp", + "description": "Descubre cómo un ERP puede transformar tu empresa.", + "keywords": ["erp", "ventajas", "productividad", "sistemas"], + "template": "blog_article", + "type": "blog", + "is_partial": false, + "seo_profile_id": "promocion-computadoras-gamer", + "seo_overrides": null, + "canonical_url": "https://agroform.com.mx/blog/ventajas-del-erp", + "content_blocks": [ + {"type": "text", "heading": "Beneficios del ERP", "content": "Centralización de procesos, automatización..."}, + {"type": "image", "src": "/images/blog/erp-benefits.jpg", "alt": "ERP y eficiencia empresarial"} + ], + "is_draft": false, + "is_sensitive": false + } +] diff --git a/database/data/website-admin/website_agroform_menus.json b/database/data/website-admin/website_agroform_menus.json new file mode 100644 index 0000000..30162f4 --- /dev/null +++ b/database/data/website-admin/website_agroform_menus.json @@ -0,0 +1,66 @@ +[ + { + "menu_slug": "main-header", + "title": { + "es": "Inicio", + "en": "Home" + }, + "url": "/", + "type": "custom", + "target": "_self", + "order": 1, + "icon": "ti ti-home" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Artículos", + "en": "Articles" + }, + "url": "/blog", + "type": "blog_article", + "target": "_self", + "order": 2, + "icon": "ti ti-news" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Categorías", + "en": "Categories" + }, + "url": "/blog/categorias", + "type": "blog_category", + "target": "_self", + "order": 3, + "icon": "ti ti-list" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Iniciar Sesión", + "en": "Login" + }, + "url": "/login", + "type": "custom", + "target": "_self", + "order": 4, + "icon": "ti ti-login", + "badge": "Nuevo", + "badge_color": "bg-success" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Cerrar Sesión", + "en": "Logout" + }, + "url": "/logout", + "type": "action", + "target": "_self", + "order": 5, + "icon": "ti ti-logout", + "method": "POST", + "roles": ["authenticated"] + } +] diff --git a/database/data/website-admin/website_cleanfy_contents.json b/database/data/website-admin/website_cleanfy_contents.json new file mode 100644 index 0000000..3cf18e4 --- /dev/null +++ b/database/data/website-admin/website_cleanfy_contents.json @@ -0,0 +1,192 @@ +[ + { + "site_id": 2, + "title": "Cleanfy", + "slug": "", + "description": "Bienvenido a cleanfy, soluciones tecnológicas para empresas.", + "keywords": ["cleanfy", "erp", "tecnología", "soluciones"], + "template": "home", + "type": "page", + "is_partial": false, + "seo_profile_id": "inicio-cleanfy", + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx", + "content_blocks": [ + {"type": "hero", "title": "Soluciones Tecnológicas para tu Empresa", "subtitle": "Desde ERP hasta eCommerce", "image": "/images/hero.jpg"}, + {"type": "features", "items": [{"icon": "ti ti-server", "label": "Infraestructura"}, {"icon": "ti ti-code", "label": "Desarrollo a medida"}]}, + {"type": "cta", "text": "Solicita una demo sin costo", "button": "Agendar llamada"} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Nosotros", + "slug": "nosotros", + "description": "Conoce la historia, visión y equipo de Koneko.", + "keywords": ["nosotros", "equipo", "historia", "koneko"], + "template": "default", + "type": "page", + "is_partial": false, + "seo_profile_id": "inicio-cleanfy", + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx/nosotros", + "content_blocks": [ + {"type": "text", "heading": "¿Quiénes somos?", "content": "Somos una empresa de tecnología con sede en México..."}, + {"type": "team", "members": [{"name": "Arturo", "role": "Director Técnico"}, {"name": "Laura", "role": "Marketing"}]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Servicios", + "slug": "servicios", + "description": "Explora nuestros servicios: ERP, eCommerce, hosting y más.", + "keywords": ["erp", "servicios", "cleanfy", "sistemas"], + "template": "services", + "type": "page", + "is_partial": false, + "seo_profile_id": "servicios-cleanfy", + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx/servicios", + "content_blocks": [ + {"type": "service_list", "items": [ + {"title": "ERP Cleanfy", "description": "Sistema de gestión empresarial completo"}, + {"title": "Tienda Virtual", "description": "Ecommerce 100% administrable con SEO nativo"}, + {"title": "Infraestructura", "description": "Servidores, VPS, Proxmox y más"} + ]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Contacto", + "slug": "contacto", + "description": "Contáctanos para resolver tus dudas o agendar una demo.", + "keywords": ["contacto", "soporte", "demo", "ayuda"], + "template": "contact", + "type": "page", + "is_partial": false, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx/contacto", + "content_blocks": [ + {"type": "form", "fields": ["nombre", "email", "mensaje"], "submit_label": "Enviar mensaje"}, + {"type": "map", "lat": 19.4326, "lng": -99.1332} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Aviso de Privacidad", + "slug": "aviso-privacidad", + "description": "Consulta nuestro aviso de privacidad actualizado.", + "keywords": ["legal", "privacidad", "datos personales"], + "template": "legal", + "type": "page", + "is_partial": false, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx/aviso-privacidad", + "content_blocks": [ + {"type": "text", "heading": "Política de privacidad", "content": "Tus datos son importantes para nosotros..."} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Preguntas Frecuentes", + "slug": "faq", + "description": "Resuelve tus dudas rápidamente con nuestras respuestas frecuentes.", + "keywords": ["faq", "dudas", "soporte"], + "template": "faq", + "type": "page", + "is_partial": true, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "faq", "items": [ + {"q": "¿Tienen soporte técnico?", "a": "Sí, disponible 24/7 para clientes activos."}, + {"q": "¿Qué formas de pago aceptan?", "a": "Transferencia, tarjeta y criptomonedas."} + ]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Header Principal", + "slug": "header-principal", + "description": "Encabezado superior con navegación.", + "keywords": [], + "template": null, + "type": "partial", + "is_partial": true, + "seo_profile_id": 1, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "menu", "menu_slug": "principal"} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Footer General", + "slug": "footer-general", + "description": "Pie de página con enlaces y datos legales.", + "keywords": [], + "template": null, + "type": "partial", + "is_partial": true, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "footer", "links": ["inicio", "servicios", "contacto"]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Categoría: Blog", + "slug": "blog", + "description": "Explora nuestros artículos de tecnología y negocios.", + "keywords": ["blog", "noticias", "tips", "artículos"], + "template": "blog_index", + "type": "category", + "is_partial": false, + "seo_profile_id": "blog", + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx/blog", + "content_blocks": [], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 2, + "title": "Artículo: Ventajas del ERP", + "slug": "blog/ventajas-del-erp", + "description": "Descubre cómo un ERP puede transformar tu empresa.", + "keywords": ["erp", "ventajas", "productividad", "sistemas"], + "template": "blog_article", + "type": "blog", + "is_partial": false, + "seo_profile_id": "promocion-computadoras-gamer", + "seo_overrides": null, + "canonical_url": "https://cleanfy.mx/blog/ventajas-del-erp", + "content_blocks": [ + {"type": "text", "heading": "Beneficios del ERP", "content": "Centralización de procesos, automatización..."}, + {"type": "image", "src": "/images/blog/erp-benefits.jpg", "alt": "ERP y eficiencia empresarial"} + ], + "is_draft": false, + "is_sensitive": false + } +] diff --git a/database/data/website-admin/website_cleanfy_menus.json b/database/data/website-admin/website_cleanfy_menus.json new file mode 100644 index 0000000..ceb9052 --- /dev/null +++ b/database/data/website-admin/website_cleanfy_menus.json @@ -0,0 +1,74 @@ +[ + { + "menu_slug": "main-header", + "title": { + "es": "Inicio", + "en": "Home" + }, + "route_name": "website.home", + "type": "custom", + "target": "_self", + "order": 1, + "icon": "ti ti-home" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Nosotros", + "en": "About Us" + }, + "route_name": "website.about", + "type": "custom", + "target": "_self", + "order": 2, + "icon": "ti ti-users" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Servicios", + "en": "Services" + }, + "route_name": "website.services", + "type": "custom", + "target": "_self", + "order": 3, + "icon": "ti ti-briefcase" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Portafolio", + "en": "Portfolio" + }, + "route_name": "website.portfolio", + "type": "custom", + "target": "_self", + "order": 4, + "icon": "ti ti-photo" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Testimonios", + "en": "Testimonials" + }, + "route_name": "website.testimonials", + "type": "custom", + "target": "_self", + "order": 5, + "icon": "ti ti-messages" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Contacto", + "en": "Contact" + }, + "route_name": "website.contact", + "type": "custom", + "target": "_self", + "order": 6, + "icon": "ti ti-phone-call" + } +] diff --git a/database/data/website-admin/website_menus.json b/database/data/website-admin/website_menus.json new file mode 100644 index 0000000..b216ff7 --- /dev/null +++ b/database/data/website-admin/website_menus.json @@ -0,0 +1,44 @@ +[ + { + "site_id": 1, + "slug": "main-header", + "title": "Menú Principal", + "description": "Navegación principal del sitio web Cleanfy.", + "is_active": true + }, + { + "site_id": 1, + "slug": "footer", + "title": "Menú Footer", + "description": "Navegación del footer del sitio web Cleanfy.", + "is_active": true + }, + { + "site_id": 2, + "slug": "main-header", + "title": "Menú Principal", + "description": "Navegación principal del sitio web Agroform.", + "is_active": true + }, + { + "site_id": 2, + "slug": "footer", + "title": "Menú Footer", + "description": "Navegación del footer del sitio web Agroform.", + "is_active": true + }, + { + "site_id": 3, + "slug": "main-header", + "title": "Menú Principal", + "description": "Navegación principal del sitio web Realcity.", + "is_active": true + }, + { + "site_id": 3, + "slug": "footer", + "title": "Menú Footer", + "description": "Navegación del footer del sitio web Realcity.", + "is_active": true + } +] diff --git a/database/data/website-admin/website_realcity_contents.json b/database/data/website-admin/website_realcity_contents.json new file mode 100644 index 0000000..812e174 --- /dev/null +++ b/database/data/website-admin/website_realcity_contents.json @@ -0,0 +1,192 @@ +[ + { + "site_id": 3, + "title": "Realcity", + "slug": "", + "description": "Bienvenido a realcity, soluciones tecnológicas para empresas.", + "keywords": ["realcity", "erp", "tecnología", "soluciones"], + "template": "home", + "type": "page", + "is_partial": false, + "seo_profile_id": "inicio-realcity", + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx", + "content_blocks": [ + {"type": "hero", "title": "Soluciones Tecnológicas para tu Empresa", "subtitle": "Desde ERP hasta eCommerce", "image": "/images/hero.jpg"}, + {"type": "features", "items": [{"icon": "ti ti-server", "label": "Infraestructura"}, {"icon": "ti ti-code", "label": "Desarrollo a medida"}]}, + {"type": "cta", "text": "Solicita una demo sin costo", "button": "Agendar llamada"} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Nosotros", + "slug": "nosotros", + "description": "Conoce la historia, visión y equipo de Koneko.", + "keywords": ["nosotros", "equipo", "historia", "koneko"], + "template": "default", + "type": "page", + "is_partial": false, + "seo_profile_id": "inicio-realcity", + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx/nosotros", + "content_blocks": [ + {"type": "text", "heading": "¿Quiénes somos?", "content": "Somos una empresa de tecnología con sede en México..."}, + {"type": "team", "members": [{"name": "Arturo", "role": "Director Técnico"}, {"name": "Laura", "role": "Marketing"}]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Servicios", + "slug": "servicios", + "description": "Explora nuestros servicios: ERP, eCommerce, hosting y más.", + "keywords": ["erp", "servicios", "realcity", "sistemas"], + "template": "services", + "type": "page", + "is_partial": false, + "seo_profile_id": "servicios-realcity", + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx/servicios", + "content_blocks": [ + {"type": "service_list", "items": [ + {"title": "ERP Realcity", "description": "Sistema de gestión empresarial completo"}, + {"title": "Tienda Virtual", "description": "Ecommerce 100% administrable con SEO nativo"}, + {"title": "Infraestructura", "description": "Servidores, VPS, Proxmox y más"} + ]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Contacto", + "slug": "contacto", + "description": "Contáctanos para resolver tus dudas o agendar una demo.", + "keywords": ["contacto", "soporte", "demo", "ayuda"], + "template": "contact", + "type": "page", + "is_partial": false, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx/contacto", + "content_blocks": [ + {"type": "form", "fields": ["nombre", "email", "mensaje"], "submit_label": "Enviar mensaje"}, + {"type": "map", "lat": 19.4326, "lng": -99.1332} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Aviso de Privacidad", + "slug": "aviso-privacidad", + "description": "Consulta nuestro aviso de privacidad actualizado.", + "keywords": ["legal", "privacidad", "datos personales"], + "template": "legal", + "type": "page", + "is_partial": false, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx/aviso-privacidad", + "content_blocks": [ + {"type": "text", "heading": "Política de privacidad", "content": "Tus datos son importantes para nosotros..."} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Preguntas Frecuentes", + "slug": "faq", + "description": "Resuelve tus dudas rápidamente con nuestras respuestas frecuentes.", + "keywords": ["faq", "dudas", "soporte"], + "template": "faq", + "type": "page", + "is_partial": true, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "faq", "items": [ + {"q": "¿Tienen soporte técnico?", "a": "Sí, disponible 24/7 para clientes activos."}, + {"q": "¿Qué formas de pago aceptan?", "a": "Transferencia, tarjeta y criptomonedas."} + ]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Header Principal", + "slug": "header-principal", + "description": "Encabezado superior con navegación.", + "keywords": [], + "template": null, + "type": "partial", + "is_partial": true, + "seo_profile_id": 1, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "menu", "menu_slug": "principal"} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Footer General", + "slug": "footer-general", + "description": "Pie de página con enlaces y datos legales.", + "keywords": [], + "template": null, + "type": "partial", + "is_partial": true, + "seo_profile_id": null, + "seo_overrides": null, + "canonical_url": null, + "content_blocks": [ + {"type": "footer", "links": ["inicio", "servicios", "contacto"]} + ], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Categoría: Blog", + "slug": "blog", + "description": "Explora nuestros artículos de tecnología y negocios.", + "keywords": ["blog", "noticias", "tips", "artículos"], + "template": "blog_index", + "type": "category", + "is_partial": false, + "seo_profile_id": "blog", + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx/blog", + "content_blocks": [], + "is_draft": false, + "is_sensitive": false + }, + { + "site_id": 3, + "title": "Artículo: Ventajas del ERP", + "slug": "blog/ventajas-del-erp", + "description": "Descubre cómo un ERP puede transformar tu empresa.", + "keywords": ["erp", "ventajas", "productividad", "sistemas"], + "template": "blog_article", + "type": "blog", + "is_partial": false, + "seo_profile_id": "promocion-computadoras-gamer", + "seo_overrides": null, + "canonical_url": "https://realcity.com.mx/blog/ventajas-del-erp", + "content_blocks": [ + {"type": "text", "heading": "Beneficios del ERP", "content": "Centralización de procesos, automatización..."}, + {"type": "image", "src": "/images/blog/erp-benefits.jpg", "alt": "ERP y eficiencia empresarial"} + ], + "is_draft": false, + "is_sensitive": false + } +] diff --git a/database/data/website-admin/website_realcity_menus.json b/database/data/website-admin/website_realcity_menus.json new file mode 100644 index 0000000..a3065a3 --- /dev/null +++ b/database/data/website-admin/website_realcity_menus.json @@ -0,0 +1,92 @@ +[ + { + "menu_slug": "main-header", + "title": { + "es": "Inicio", + "en": "Home" + }, + "type": "cms_page", + "order": 1, + "icon": "ti ti-home" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Nuestro corporativo", + "en": "About Us" + }, + "type": "cms_page", + "order": 2, + "icon": "ti ti-users" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Soluciones y servicios", + "en": "Solutions and services" + }, + "type": "cms_page", + "order": 3, + "icon": "ti ti-briefcase" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Valor y Beneficios", + "en": "Value and Benefits" + }, + "type": "cms_page", + "order": 4, + "icon": "ti ti-briefcase" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Experiencia de éxito", + "en": "Success Experience" + }, + "type": "cms_page", + "order": 5, + "icon": "ti ti-briefcase" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Blog y contenido", + "en": "Blog and content" + }, + "type": "blog_article", + "order": 6, + "icon": "ti ti-news" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Residentes y usuarios", + "en": "Residents and users" + }, + "type": "cms_page", + "order": 7, + "icon": "ti ti-users" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Contacto", + "en": "Contact" + }, + "type": "action", + "order": 8, + "icon": "ti ti-phone-call" + }, + { + "menu_slug": "main-header", + "title": { + "es": "Legales", + "en": "Legal" + }, + "type": "cms_page", + "order": 9, + "icon": "ti ti-file" + } +] diff --git a/database/data/website-admin/website_seo_profiles.json b/database/data/website-admin/website_seo_profiles.json new file mode 100644 index 0000000..ea18d03 --- /dev/null +++ b/database/data/website-admin/website_seo_profiles.json @@ -0,0 +1,145 @@ +[ + { + "site_id": 1, + "type": "page", + "title": "AgroForm México | Jardinería Integral con Tecnología y Sostenibilidad", + "slug": "agroform-home", + "description": "AgroForm México transforma espacios verdes desde 2017 con servicios de jardinería, mantenimiento y diseño innovador. Combinamos sostenibilidad, tecnología y excelencia para embellecer y proteger tu inversión.", + "schema_org": { + "@context": "https://schema.org", + "@type": "Organization", + "name": "AgroForm México", + "url": "https://agroform.com.mx", + "logo": "https://agroform.com.mx/assets/logo.png", + "description": "Servicios de jardinería, mantenimiento de áreas verdes y paisajismo sostenible en México.", + "foundingDate": "2017", + "areaServed": "MX", + "sameAs": [ + "https://www.facebook.com/agroformmx", + "https://www.instagram.com/agroformmx" + ] + }, + "noindex": false, + "nofollow": false, + "locale": "es-MX", + "geo_location": { + "geo.region": "MX", + "geo.placename": "México" + }, + "og_type": "website", + "og_title": "AgroForm México | Jardinería Integral con Tecnología y Sostenibilidad", + "og_description": "Creamos y cuidamos jardines, parques y espacios verdes sostenibles con tecnología de punta y personal altamente capacitado.", + "og_image": "https://agroform.com.mx/assets/og-image.jpg", + "og_url": "https://agroform.com.mx", + "og_site_name": "AgroForm México", + "twitter_card": "summary_large_image", + "twitter_title": "AgroForm México", + "twitter_description": "Soluciones integrales en jardinería y paisajismo sostenible para parques y espacios verdes.", + "twitter_image": "https://agroform.com.mx/assets/twitter-card.jpg", + "twitter_site": "@agroformmx", + "twitter_creator": "@agroformmx", + "json_ld": { + "@context": "https://schema.org", + "@type": "WebPage", + "name": "AgroForm México", + "url": "https://agroform.com.mx", + "description": "Empresa mexicana líder en jardinería integral, mantenimiento de áreas verdes y paisajismo con enfoque tecnológico y sostenible." + }, + "created_by": 1, + "updated_by": 1, + "created_at": "2025-05-24T17:00:00", + "updated_at": "2025-05-24T17:00:00" + }, + { + "site_id": 2, + "type": "page", + "title": "CleanFy México | Soluciones Integrales de Limpieza y Sanitización", + "slug": "cleanfy-home", + "description": "CleanFy es tu socio estratégico en limpieza profesional y sanitización en México. Con personal capacitado, tecnología avanzada y enfoque sostenible, garantizamos espacios seguros, limpios y saludables.", + "schema_org": { + "@context": "https://schema.org", + "@type": "Organization", + "name": "CleanFy México", + "url": "https://cleanfy.mx", + "logo": "https://cleanfy.mx/assets/logo.png", + "description": "Empresa líder en soluciones integrales de limpieza, sanitización y mantenimiento para hogares, oficinas, industrias y más.", + "foundingDate": "2017", + "areaServed": "MX", + "sameAs": [ + "https://www.facebook.com/cleanfymx", + "https://www.instagram.com/cleanfymx" + ] + }, + "noindex": false, + "nofollow": false, + "locale": "es-MX", + "geo_location": { + "geo.region": "MX", + "geo.placename": "México" + }, + "og_type": "website", + "og_title": "CleanFy México | Soluciones Integrales de Limpieza y Sanitización", + "og_description": "Transformamos espacios en ambientes impecables y seguros. Limpieza profesional, protocolos tecnológicos, y compromiso con la calidad y la salud.", + "og_image": "https://cleanfy.mx/assets/og-image.jpg", + "og_url": "https://cleanfy.mx", + "og_site_name": "CleanFy México", + "twitter_card": "summary_large_image", + "twitter_title": "CleanFy México", + "twitter_description": "Empresa profesional de limpieza y sanitización con tecnología, compromiso y excelencia.", + "twitter_image": "https://cleanfy.mx/assets/twitter-card.jpg", + "twitter_site": "@cleanfymx", + "twitter_creator": "@cleanfymx", + "json_ld": { + "@context": "https://schema.org", + "@type": "WebPage", + "name": "CleanFy México", + "url": "https://cleanfy.mx", + "description": "Empresa mexicana especializada en limpieza y sanitización profesional para empresas, oficinas y hogares. Tecnología, personal capacitado y sostenibilidad al servicio de la salud y productividad." + } + } + , + { + "site_id": 3, + "type": "page", + "title": "RealCity México | Próximamente la Nueva Plataforma Inmobiliaria", + "slug": "realcity-coming-soon", + "description": "Prepárate para una experiencia inmobiliaria moderna y eficiente. RealCity está por llegar para transformar la forma de comprar, vender y rentar propiedades en México.", + "schema_org": { + "@context": "https://schema.org", + "@type": "Organization", + "name": "RealCity México", + "url": "https://realcity.com.mx", + "logo": "https://realcity.com.mx/assets/logo.png", + "description": "Plataforma inmobiliaria innovadora en México, especializada en conectar compradores, vendedores e inversionistas mediante tecnología moderna.", + "foundingDate": "2025", + "areaServed": "MX" + }, + "noindex": false, + "nofollow": false, + "locale": "es-MX", + "geo_location": { + "geo.region": "MX", + "geo.placename": "México" + }, + "og_type": "website", + "og_title": "RealCity México | Próximamente la Nueva Plataforma Inmobiliaria", + "og_description": "Muy pronto una nueva experiencia digital para el sector inmobiliario. Encuentra la propiedad ideal con RealCity México.", + "og_image": "https://realcity.com.mx/assets/og-image.jpg", + "og_url": "https://realcity.com.mx", + "og_site_name": "RealCity México", + "twitter_card": "summary_large_image", + "twitter_title": "RealCity México", + "twitter_description": "Innovación inmobiliaria en México. RealCity es la nueva forma de buscar, publicar y cerrar negocios en bienes raíces.", + "twitter_image": "https://realcity.com.mx/assets/twitter-card.jpg", + "twitter_site": "@realcitymx", + "twitter_creator": "@realcitymx", + "json_ld": { + "@context": "https://schema.org", + "@type": "WebPage", + "name": "RealCity México", + "url": "https://realcity.com.mx", + "description": "RealCity ofrece una plataforma tecnológica avanzada para transformar la experiencia inmobiliaria en México, con servicios digitales para compra, venta y renta de propiedades." + } + } + +] diff --git a/database/data/website-admin/website_sites.json b/database/data/website-admin/website_sites.json new file mode 100644 index 0000000..950d5ee --- /dev/null +++ b/database/data/website-admin/website_sites.json @@ -0,0 +1,220 @@ +[ + { + "name": "cleanfy.mx", + "slug": "cleanfy-mx", + "domain": "cleanfy.mx", + "template": "cleaning-services", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Limpieza Profesional - Cleanfy", + "meta_description": "Servicios de limpieza profesional y desinfección para empresas y hogares en México.", + "canonical_url": "https://cleanfy.mx", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "favicon": "/assets/cleanfy/favicon.ico", + "primary_color": "#27ae60", + "logo": "/assets/cleanfy/logo.svg" + }, + "social": { + "og:title": "Cleanfy.mx", + "og:image": "/assets/cleanfy/og-image.jpg", + "twitter:card": "summary_large_image", + "twitter:site": "@cleanfymx" + } + }, + { + "name": "agroform.com.mx", + "slug": "agroform-com-mx", + "domain": "agroform.com.mx", + "template": "renewable-energy", + "status": "coming_soon", + "is_active": true, + "is_indexable": false, + "meta_title": "AgroForm | Agricultura Inteligente", + "meta_description": "Soluciones tecnológicas para productores agrícolas: sensores, plataformas y más.", + "canonical_url": "https://agroform.com.mx", + "locale": "es-MX", + "robots_directives": "noindex,nofollow", + "config": { + "maintenance_page": "coming-soon", + "primary_color": "#388e3c", + "logo": "/assets/agroform/logo.svg" + }, + "social": { + "og:title": "AgroForm", + "og:description": "Innovación para el campo mexicano.", + "twitter:site": "@agroformmx" + } + }, + { + "name": "realcity.com.mx", + "slug": "realcity-com-mx", + "domain": "realcity.com.mx", + "template": "business-consulting-4", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "RealCity | Bienes Raíces Premium", + "meta_description": "Compra y renta de propiedades en zonas exclusivas. Encuentra tu próximo hogar con RealCity.", + "canonical_url": "https://realcity.com.mx", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "template_variant": "real-estate", + "primary_color": "#34495e", + "logo": "/assets/realcity/logo.svg" + }, + "social": { + "og:title": "RealCity", + "og:image": "/assets/realcity/og-image.jpg", + "og:type": "website", + "twitter:site": "@realcitymx" + } + }, + { + "name": "anonymous.test", + "slug": "anonymous-test", + "domain": "anonymous.test", + "template": "anonymous", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Anonymous", + "meta_description": "Anonymous", + "canonical_url": "https://anonymous.test", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "primary_color": "#34495e", + "logo": "/assets/anonymous/logo.svg" + }, + "social": { + "og:title": "Anonymous", + "og:image": "/assets/anonymous/og-image.jpg", + "og:type": "website", + "twitter:site": "@anonymousmx" + } + }, + { + "name": "landwind.test", + "slug": "landwind-test", + "domain": "landwind.test", + "template": "landwind", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Landwind", + "meta_description": "Landwind", + "canonical_url": "https://landwind.test", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "primary_color": "#34495e", + "logo": "/assets/landwind/logo.svg" + }, + "social": { + "og:title": "Landwind", + "og:image": "/assets/landwind/og-image.jpg", + "og:type": "website", + "twitter:site": "@landwindmx" + } + }, + { + "name": "limaa-m.test", + "slug": "limaa-m-test", + "domain": "limaa-m.test", + "template": "limaa-m", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Limaa M", + "meta_description": "Limaa M", + "canonical_url": "https://limaa-m.test", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "primary_color": "#34495e", + "logo": "/assets/limaa-m/logo.svg" + }, + "social": { + "og:title": "Limaa M", + "og:image": "/assets/limaa-m/og-image.jpg", + "og:type": "website", + "twitter:site": "@limaa-mmx" + } + }, + { + "name": "maximus.test", + "slug": "maximus-test", + "domain": "maximus.test", + "template": "maximus", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Maximus", + "meta_description": "Maximus", + "canonical_url": "https://maximus.test", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "primary_color": "#34495e", + "logo": "/assets/maximus/logo.svg" + }, + "social": { + "og:title": "Maximus", + "og:image": "/assets/maximus/og-image.jpg", + "og:type": "website", + "twitter:site": "@maximusmx" + } + }, + { + "name": "notus.test", + "slug": "notus-test", + "domain": "notus.test", + "template": "notus", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Notus", + "meta_description": "Notus", + "canonical_url": "https://notus.test", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "primary_color": "#34495e", + "logo": "/assets/notus/logo.svg" + }, + "social": { + "og:title": "Notus", + "og:image": "/assets/notus/og-image.jpg", + "og:type": "website", + "twitter:site": "@notusmx" + } + }, + { + "name": "samuel-coming-soon.test", + "slug": "samuel-coming-soon-test", + "domain": "samuel-coming-soon.test", + "template": "samuel-coming-soon", + "status": "active", + "is_active": true, + "is_indexable": true, + "meta_title": "Samuel Coming Soon", + "meta_description": "Samuel Coming Soon", + "canonical_url": "https://samuel-coming-soon.test", + "locale": "es-MX", + "robots_directives": "index,follow", + "config": { + "primary_color": "#34495e", + "logo": "/assets/samuel-coming-soon/logo.svg" + }, + "social": { + "og:title": "Samuel Coming Soon", + "og:image": "/assets/samuel-coming-soon/og-image.jpg", + "og:type": "website", + "twitter:site": "@samuelcomingsoonmx" + } + } + ] diff --git a/database/migrations/2024_12_29_081809_create_website_seo_profiles_table.php b/database/migrations/2024_12_29_081809_create_website_seo_profiles_table.php new file mode 100644 index 0000000..68aada7 --- /dev/null +++ b/database/migrations/2024_12_29_081809_create_website_seo_profiles_table.php @@ -0,0 +1,77 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('site_id')->nullable()->index(); + + // Metadata + $table->string('type', 16)->default('page')->index(); // Enum: Content, Landing, Product, Category Blog + $table->string('title')->nullable()->index(); // Título del perfil + $table->string('slug')->unique(); + $table->mediumText('description')->nullable(); // Descripción del perfil + + // Tipos de schema adicionales + $table->json('schema_org')->nullable(); + + // Robots Directives + $table->boolean('noindex')->default(false); + $table->boolean('nofollow')->default(false); + + // Idioma y Geolocalización + $table->string('locale', 8)->default('es-MX')->index(); // Para SEO internacional + $table->json('geo_location')->nullable(); // meta geo.region y geo.placename + + // Open Graph + $table->string('og_type')->nullable(); + $table->string('og_title')->nullable(); + $table->text('og_description')->nullable(); + $table->string('og_image')->nullable(); + $table->string('og_url')->nullable(); + $table->string('og_site_name')->nullable(); + + // Twitter Card + $table->string('twitter_card')->default('summary_large_image'); + $table->string('twitter_title')->nullable(); + $table->text('twitter_description')->nullable(); + $table->string('twitter_image')->nullable(); + $table->string('twitter_site')->nullable(); + $table->string('twitter_creator')->nullable(); + + // JSON-LD opcional (almacenado como bloque JSON) + $table->json('json_ld')->nullable(); + + // Auditoria + $table->unsignedMediumInteger('created_by')->nullable(); + $table->unsignedMediumInteger('updated_by')->nullable(); + $table->timestamps(); + + // Indices + $table->index(['site_id', 'type']); + $table->index(['site_id', 'type', 'slug']); + + // Relaciones + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_seo_profiles'); + } +}; diff --git a/database/migrations/2024_12_29_081811_create_website_sites_table.php b/database/migrations/2024_12_29_081811_create_website_sites_table.php new file mode 100644 index 0000000..4610b3a --- /dev/null +++ b/database/migrations/2024_12_29_081811_create_website_sites_table.php @@ -0,0 +1,64 @@ +smallIncrements('id'); + + // Identidad + $table->string('name')->index(); // Nombre visible en admin + $table->string('slug')->unique(); // Clave técnica + $table->string('domain')->unique(); // Dominio principal (sin protocolo) + $table->string('template')->nullable(); // Componente layout activo + + // Estado + $table->string('status', 16)->default('active')->index(); // Estados especiales del sitio + $table->boolean('is_indexable')->default(true)->index(); // SEO: permitir indexado o no + + // SEO + $table->unsignedSmallInteger('seo_profile_id')->nullable()->index(); + $table->string('canonical_url')->nullable(); // Canonical para root + + // Configuración + $table->json('config')->nullable(); // favicon, theme, brand, CDN, etc. + + // Auditoría + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + $table->timestamps(); + + // Indices + $table->index(['slug', 'status', 'is_indexable']); + + // Relaciones + $table->foreign('seo_profile_id')->references('id')->on('website_seo_profiles')->nullOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + + Schema::table('website_seo_profiles', function (Blueprint $table) { + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_sites'); + + Schema::table('website_seo_profiles', function (Blueprint $table) { + $table->dropForeign(['site_id']); + }); + } +}; diff --git a/database/migrations/2024_12_29_081815_create_website_menus_table.php b/database/migrations/2024_12_29_081815_create_website_menus_table.php new file mode 100644 index 0000000..91bd373 --- /dev/null +++ b/database/migrations/2024_12_29_081815_create_website_menus_table.php @@ -0,0 +1,48 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('site_id')->index(); + + $table->string('title')->index(); + $table->string('slug')->unique(); + + $table->string('description')->nullable(); + $table->boolean('is_active')->default(true)->index(); + + // Auditoría + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + + $table->timestamps(); + + // Indices + $table->index(['site_id', 'slug', 'is_active']); + + // Relaciones + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_menus'); + } +}; diff --git a/database/migrations/2024_12_29_081816_create_website_menu_items_table.php b/database/migrations/2024_12_29_081816_create_website_menu_items_table.php new file mode 100644 index 0000000..c867b4a --- /dev/null +++ b/database/migrations/2024_12_29_081816_create_website_menu_items_table.php @@ -0,0 +1,75 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('menu_id')->index(); + $table->unsignedSmallInteger('parent_id')->nullable()->index(); + + $table->json('title'); // i18n multilanguage + $table->string('type', 16)->default('cms_page')->index(); // Enum; cms_page | url | laravel_route | blog_article | Evento Json + + // Construcción de enlace + $table->unsignedMediumInteger('linkable_id')->nullable()->index(); // Relación polimórfica con: + $table->string('linkable_type')->nullable()->index(); // páginas, entradas, productos, etc. + + $table->string('laravel_route')->nullable()->index(); + $table->string('url')->nullable()->index(); + $table->string('method')->nullable(); + $table->string('target', 16)->nullable(); // Enum _self, _blank, etc. + $table->string('js_event')->nullable(); + + // UI + $table->string('icon')->nullable(); + $table->string('badge')->nullable(); + $table->string('badge_color')->nullable(); + + // Visibilidad + $table->json('roles')->nullable(); + $table->json('permissions')->nullable(); + $table->boolean('hide_if_authenticated')->default(false)->index(); + $table->boolean('hide_if_guest')->default(false)->index(); + $table->timestamp('visible_from')->nullable(); + $table->timestamp('visible_until')->nullable(); + + $table->unsignedSmallInteger('order')->default(0); // Para ordenar en el menú + $table->boolean('is_active')->default(true)->index(); + + // Auditoría + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + + $table->timestamps(); + + // Indices + $table->index(['menu_id', 'is_active']); + $table->index(['menu_id', 'parent_id', 'is_active']); + $table->index(['linkable_id', 'linkable_type']); + + // Relaciones + $table->foreign('menu_id')->references('id')->on('website_menus')->cascadeOnDelete(); + $table->foreign('parent_id')->references('id')->on('website_menu_items')->nullOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_menu_items'); + } +}; diff --git a/database/migrations/2024_12_29_082518_create_website_contents_table.php b/database/migrations/2024_12_29_082518_create_website_contents_table.php new file mode 100644 index 0000000..f69d6cb --- /dev/null +++ b/database/migrations/2024_12_29_082518_create_website_contents_table.php @@ -0,0 +1,84 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('site_id')->index(); + $table->unsignedSmallInteger('seo_profile_id')->nullable()->index(); + + // Metadata + $table->string('title')->index(); + $table->string('slug')->unique(); + $table->string('description'); + $table->json('keywords')->nullable(); + + // Template & Type content + $table->string('template')->nullable(); + $table->string('template_variant')->nullable(); + $table->string('type', 16)->default('page')->index(); // Enum: Content, Landing, Product, Category Blog, Partial + $table->string('render_mode', 16)->default('static'); + $table->string('block_mode', 16)->default('db'); + $table->string('source', 16)->default('db'); + $table->string('render_as')->nullable(); + + //canonical url + $table->string('canonical_url')->nullable(); + + // Content + $table->json('content_blocks')->nullable(); // Bloques estructurados + $table->json('seo_overrides')->nullable(); + + // Control + $table->boolean('is_draft')->default(true)->index(); + $table->boolean('is_sensitive')->default(false)->index(); // Allow dangerous Blade content + $table->boolean('is_partial')->default(false)->index(); + + // Visibilidad + $table->json('roles')->nullable(); + $table->json('permissions')->nullable(); + $table->boolean('hide_if_authenticated')->default(false)->index(); + $table->boolean('hide_if_guest')->default(false)->index(); + $table->timestamp('visible_from')->nullable(); + $table->timestamp('visible_until')->nullable(); + + // Cache + $table->boolean('enable_cache')->default(true)->index(); + $table->unsignedSmallInteger('cache_ttl')->default(60); // minutos + + // Auditoria + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + + $table->timestamps(); + + // Indices + $table->index(['site_id', 'type']); + $table->index(['site_id', 'type', 'slug']); + + // Relaciones + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + $table->foreign('seo_profile_id')->references('id')->on('website_seo_profiles')->nullOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_contents'); + } +}; diff --git a/database/migrations/2024_12_29_082520_create_website_content_blocks_table.php b/database/migrations/2024_12_29_082520_create_website_content_blocks_table.php new file mode 100644 index 0000000..233a943 --- /dev/null +++ b/database/migrations/2024_12_29_082520_create_website_content_blocks_table.php @@ -0,0 +1,48 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('content_id')->index(); + $table->unsignedSmallInteger('parent_id')->nullable()->index(); + + $table->string('slug')->nullable(); + $table->string('type', 32); + $table->string('mode', 16)->default('view'); + $table->string('view_path')->nullable(); + $table->string('component_class')->nullable(); + + $table->boolean('is_enabled')->default(true); + $table->boolean('enable_cache')->default(true); + $table->unsignedSmallInteger('cache_ttl')->default(60); + + $table->json('settings')->nullable(); + $table->json('data')->nullable(); + $table->unsignedSmallInteger('order')->default(0); + + $table->timestamps(); + + $table->foreign('content_id')->references('id')->on('website_contents')->cascadeOnDelete(); + $table->foreign('parent_id')->references('id')->on('website_content_blocks')->nullOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_content_blocks'); + } +}; diff --git a/database/migrations/2024_12_29_082521_create_website_content_versions_table.php b/database/migrations/2024_12_29_082521_create_website_content_versions_table.php new file mode 100644 index 0000000..6c55a5d --- /dev/null +++ b/database/migrations/2024_12_29_082521_create_website_content_versions_table.php @@ -0,0 +1,42 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('website_content_id')->index(); + $table->string('version_label')->nullable(); + $table->longText('content'); + $table->json('metadata')->nullable(); + + // Auditoria + $table->unsignedMediumInteger('created_by')->nullable(); + $table->unsignedMediumInteger('updated_by')->nullable(); + + $table->timestamps(); + + // Relaciones + $table->foreign('website_content_id')->references('id')->on('website_contents')->cascadeOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('website_content_versions'); + } +}; diff --git a/database/migrations/2024_12_29_090535_create_sitemap_profiles_table.php b/database/migrations/2024_12_29_090535_create_sitemap_profiles_table.php new file mode 100644 index 0000000..52b0b01 --- /dev/null +++ b/database/migrations/2024_12_29_090535_create_sitemap_profiles_table.php @@ -0,0 +1,44 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('site_id')->index(); // Soporte multisite + + $table->string('name'); // Nombre del perfil: 'Productos', 'Páginas CMS' + $table->string('slug')->unique(); // Clave técnica + + $table->string('entity_type')->nullable(); // Ej: App\Models\Product + $table->string('generator_class')->nullable(); // Clase que implementa SitemapUrlGeneratorInterface + + $table->boolean('is_active')->default(true)->index(); + // Auditoría + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + + $table->timestamps(); + + // Indices + $table->index(['site_id', 'slug']); + $table->index(['site_id', 'slug', 'is_active']); + + // Relaciones + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + public function down(): void + { + Schema::dropIfExists('sitemap_profiles'); + } +}; diff --git a/database/migrations/2024_12_29_090537_create_sitemap_rules_table.php b/database/migrations/2024_12_29_090537_create_sitemap_rules_table.php new file mode 100644 index 0000000..88f426a --- /dev/null +++ b/database/migrations/2024_12_29_090537_create_sitemap_rules_table.php @@ -0,0 +1,32 @@ +mediumIncrements('id'); + + $table->unsignedSmallInteger('sitemap_profile_id')->index(); + $table->string('rule_type'); // Ej: 'priority_override', 'exclude_flag', etc. + $table->json('rule_data'); // JSON con parámetros + + $table->timestamps(); + + // Indices + $table->index(['sitemap_profile_id', 'rule_type']); + + // Relaciones + $table->foreign('sitemap_profile_id')->references('id')->on('sitemap_profiles')->cascadeOnDelete(); + }); + } + + public function down(): void + { + Schema::dropIfExists('sitemap_rules'); + } +}; diff --git a/database/migrations/create_sitemap_urls_table.php b/database/migrations/2024_12_29_090539_create_sitemap_urls_table.php similarity index 53% rename from database/migrations/create_sitemap_urls_table.php rename to database/migrations/2024_12_29_090539_create_sitemap_urls_table.php index 56a1541..6bb7b9b 100644 --- a/database/migrations/create_sitemap_urls_table.php +++ b/database/migrations/2024_12_29_090539_create_sitemap_urls_table.php @@ -12,13 +12,26 @@ return new class extends Migration public function up(): void { Schema::create('sitemap_urls', function (Blueprint $table) { - $table->id(); + $table->mediumIncrements('id'); + + $table->unsignedSmallInteger('sitemap_profile_id')->index(); $table->string('url')->unique(); - $table->enum('changefreq', ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'])->default('weekly'); + + $table->string('changefreq', 16)->default('weekly'); // Enum $table->decimal('priority', 2, 1)->default(0.5); $table->timestamp('lastmod')->nullable(); - $table->boolean('is_active')->default(true); + + $table->boolean('is_active')->default(true)->index(); + $table->json('alternate_locales')->nullable(); // SEO internacional + + // Auditoría $table->timestamps(); + + // Indices + $table->index(['sitemap_profile_id', 'is_active']); + + // Relaciones + $table->foreign('sitemap_profile_id')->references('id')->on('sitemap_profiles')->cascadeOnDelete(); }); } diff --git a/database/migrations/2024_12_29_090548_create_sitemap_index_files_table.php b/database/migrations/2024_12_29_090548_create_sitemap_index_files_table.php new file mode 100644 index 0000000..5390c20 --- /dev/null +++ b/database/migrations/2024_12_29_090548_create_sitemap_index_files_table.php @@ -0,0 +1,32 @@ +mediumIncrements('id'); + + $table->unsignedSmallInteger('sitemap_profile_id')->index(); + $table->string('file_name')->unique(); + $table->string('url'); + $table->timestamp('generated_at'); + + $table->integer('url_count')->default(0); + $table->boolean('is_current')->default(true); + + $table->timestamps(); + + $table->foreign('sitemap_profile_id')->references('id')->on('sitemap_profiles')->cascadeOnDelete(); + }); + } + + public function down(): void + { + Schema::dropIfExists('sitemap_index_files'); + } +}; diff --git a/database/migrations/2024_12_29_081812_create_faq_categories_table.php b/database/migrations/2024_12_30_082012_create_faq_categories_table.php similarity index 50% rename from database/migrations/2024_12_29_081812_create_faq_categories_table.php rename to database/migrations/2024_12_30_082012_create_faq_categories_table.php index c2877d8..d370876 100644 --- a/database/migrations/2024_12_29_081812_create_faq_categories_table.php +++ b/database/migrations/2024_12_30_082012_create_faq_categories_table.php @@ -14,13 +14,27 @@ return new class extends Migration Schema::create('faq_categories', function (Blueprint $table) { $table->smallIncrements('id'); - $table->string('name')->unique(); + $table->unsignedSmallInteger('site_id')->index(); // Soporte multisite + + $table->string('name')->index(); $table->string('icon')->nullable(); $table->unsignedInteger('order')->default(0)->index(); $table->boolean('is_active')->default(true)->index(); // Auditoria + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + $table->timestamps(); + + // Indices + $table->unique(['name', 'site_id']); + $table->index(['name', 'is_active', 'site_id']); + + // Relaciones + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); }); } diff --git a/database/migrations/2024_12_29_081815_create_faqs_table.php b/database/migrations/2024_12_30_082017_create_faqs_table.php similarity index 63% rename from database/migrations/2024_12_29_081815_create_faqs_table.php rename to database/migrations/2024_12_30_082017_create_faqs_table.php index 22ebe1f..486a4fa 100644 --- a/database/migrations/2024_12_29_081815_create_faqs_table.php +++ b/database/migrations/2024_12_30_082017_create_faqs_table.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::create('faqs', function (Blueprint $table) { - $table->id(); + $table->smallIncrements('id'); $table->unsignedSmallInteger('category_id')->nullable()->index(); $table->string('question'); @@ -21,10 +21,18 @@ return new class extends Migration $table->boolean('is_active')->default(true)->index(); // Auditoria + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + $table->timestamps(); + // Indices + $table->index(['category_id', 'is_active']); + // Relaciones - $table->foreign('category_id')->references('id')->on('faq_categories')->nullOnDelete(); + $table->foreign('category_id')->references('id')->on('faq_categories')->restrictOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); }); } diff --git a/database/migrations/2024_12_30_082125_create_blog_categories_table.php b/database/migrations/2024_12_30_082125_create_blog_categories_table.php new file mode 100644 index 0000000..a99754b --- /dev/null +++ b/database/migrations/2024_12_30_082125_create_blog_categories_table.php @@ -0,0 +1,60 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('site_id')->index(); // Soporte multisite + + $table->string('name')->index(); + $table->string('slug')->index(); + $table->string('full_slug')->nullable()->index(); // NUEVO: Ruta cacheada + + $table->unsignedSmallInteger('parent_id')->nullable()->index(); + + $table->string('icon')->nullable(); // NUEVO: Soporte para icono + $table->string('group')->nullable()->index(); // NUEVO: Soporte para agrupación (ej: blog, noticias, promociones) + + + $table->text('description')->nullable(); + $table->boolean('is_active')->default(true)->index(); + + // Auditoría + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + + $table->timestamps(); + + // Relaciones + $table->foreign('parent_id')->references('id')->on('blog_categories')->nullOnDelete(); + + // Indices + $table->index(['name', 'is_active', 'site_id']); + $table->unique(['slug', 'site_id']); + + // Relaciones + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blog_categories'); + } +}; diff --git a/database/migrations/2024_12_30_082126_create_blog_tags_table.php b/database/migrations/2024_12_30_082126_create_blog_tags_table.php new file mode 100644 index 0000000..899b36f --- /dev/null +++ b/database/migrations/2024_12_30_082126_create_blog_tags_table.php @@ -0,0 +1,48 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('site_id')->index(); // Soporte multisite + + $table->string('name')->index(); + $table->string('slug')->index(); + + $table->boolean('is_active')->default(true)->index(); + + // Auditoria + $table->unsignedMediumInteger('created_by')->nullable()->index(); + $table->unsignedMediumInteger('updated_by')->nullable()->index(); + + $table->timestamps(); + + // Indices + $table->unique(['slug', 'site_id']); + $table->index(['name', 'is_active']); + + // Relaciones + $table->foreign('site_id')->references('id')->on('website_sites')->cascadeOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blog_tags'); + } +}; diff --git a/database/migrations/2024_12_30_082127_create_blog_articles_table.php b/database/migrations/2024_12_30_082127_create_blog_articles_table.php new file mode 100644 index 0000000..decdc4e --- /dev/null +++ b/database/migrations/2024_12_30_082127_create_blog_articles_table.php @@ -0,0 +1,53 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('category_id')->index(); + + $table->string('title')->index(); + $table->string('slug')->index(); + + $table->text('excerpt')->nullable(); + $table->longText('content'); + $table->json('metadata')->nullable(); + + $table->boolean('is_published')->default(false)->index(); + $table->timestamp('published_at')->nullable()->index(); + + $table->unsignedMediumInteger('created_by')->index(); + $table->unsignedMediumInteger('updated_by')->index(); + + // Auditoria + $table->timestamps(); + + // Indices + $table->unique(['slug', 'category_id']); + $table->index(['title', 'is_published']); + + // Relaciones + $table->foreign('category_id')->references('id')->on('blog_categories')->restrictOnDelete(); + $table->foreign('created_by')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blog_articles'); + } +}; diff --git a/database/migrations/2024_12_30_082128_create_blog_article_tag_table.php b/database/migrations/2024_12_30_082128_create_blog_article_tag_table.php new file mode 100644 index 0000000..5c0e6d9 --- /dev/null +++ b/database/migrations/2024_12_30_082128_create_blog_article_tag_table.php @@ -0,0 +1,39 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('blog_article_id')->index(); + $table->unsignedSmallInteger('blog_tag_id')->index(); + + // Auditoria + $table->timestamps(); + + // Indices + $table->index(['blog_article_id', 'blog_tag_id']); + + // Relaciones + $table->foreign('blog_article_id')->references('id')->on('blog_articles')->cascadeOnDelete(); + $table->foreign('blog_tag_id')->references('id')->on('blog_tags')->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blog_article_tag'); + } +}; diff --git a/database/migrations/2024_12_30_082129_create_blog_comments_table.php b/database/migrations/2024_12_30_082129_create_blog_comments_table.php new file mode 100644 index 0000000..85a6815 --- /dev/null +++ b/database/migrations/2024_12_30_082129_create_blog_comments_table.php @@ -0,0 +1,47 @@ +mediumIncrements('id'); + + $table->unsignedSmallInteger('blog_article_id')->index(); + + $table->unsignedMediumInteger('author_id')->nullable()->index(); + $table->string('author_name')->index(); + $table->string('author_email')->index(); + $table->text('comment'); + + $table->boolean('is_approved')->default(false)->index(); + + // Auditoria + $table->unsignedMediumInteger('updated_by')->index(); + $table->timestamps(); + + // Indices + $table->index(['blog_article_id', 'is_approved']); + + // Relaciones + $table->foreign('blog_article_id')->references('id')->on('blog_articles')->restrictOnDelete(); + $table->foreign('author_id')->references('id')->on('users')->restrictOnDelete(); + $table->foreign('updated_by')->references('id')->on('users')->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blog_comments'); + } +}; diff --git a/database/migrations/create_sitemap_configurations_table.php b/database/migrations/create_sitemap_configurations_table.php deleted file mode 100644 index 9c53366..0000000 --- a/database/migrations/create_sitemap_configurations_table.php +++ /dev/null @@ -1,23 +0,0 @@ -id(); - $table->string('route'); - $table->boolean('include')->default(true); - $table->decimal('priority', 2, 1)->default(0.5); - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('sitemap_configurations'); - } -}; \ No newline at end of file diff --git a/database/rbac copy/permissions.json b/database/rbac copy/permissions.json new file mode 100644 index 0000000..260a63b --- /dev/null +++ b/database/rbac copy/permissions.json @@ -0,0 +1,70 @@ +[ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.general.update", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.social.update", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.settings.indexing.update", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.info.update", + "admin.website-admin.contact.form.view", + "admin.website-admin.contact.form.update", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-analytics.update", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-tags.update", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.google-search-console.update", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.analytics.pixel-meta.update", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.messenger.update", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.whatsapp.update", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.tawk-to.update", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.comunication.twitter.update", + "admin.website-admin.translate.google.view", + "admin.website-admin.translate.google.update", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.faq.update", + "admin.website-admin.content.faq.create", + "admin.website-admin.content.faq.delete", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.gallery.update", + "admin.website-admin.content.gallery.create", + "admin.website-admin.content.gallery.delete", + "admin.website-admin.content.legal.view", + "admin.website-admin.content.legal.update", + "admin.website-admin.content.legal.create", + "admin.website-admin.content.legal.delete", + "admin.website-admin.seo.sitemap.view", + "admin.website-admin.seo.sitemap.update", + "admin.website-admin.seo.jsonld.view", + "admin.website-admin.seo.jsonld.update", + "admin.website-admin.seo.robots.view", + "admin.website-admin.seo.robots.update", + "admin.website-admin.seo.manifest.view", + "admin.website-admin.seo.manifest.update", + "admin.website-admin.seo.canonical.view", + "admin.website-admin.seo.canonical.update", + "admin.website-admin.seo.social-cards.view", + "admin.website-admin.seo.social-cards.update", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.categories.update", + "admin.website-admin.blog.categories.create", + "admin.website-admin.blog.categories.delete", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.tags.update", + "admin.website-admin.blog.tags.create", + "admin.website-admin.blog.tags.delete", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.articles.update", + "admin.website-admin.blog.articles.create", + "admin.website-admin.blog.articles.delete", + "admin.website-admin.blog.comments.view", + "admin.website-admin.blog.comments.update", + "admin.website-admin.blog.comments.create", + "admin.website-admin.blog.comments.delete" +] diff --git a/database/rbac copy/roles.json b/database/rbac copy/roles.json new file mode 100644 index 0000000..a7b0542 --- /dev/null +++ b/database/rbac copy/roles.json @@ -0,0 +1,177 @@ +{ + "SuperAdmin" : { + "permissions" : [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.general.update", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.social.update", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.settings.indexing.update", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.info.update", + "admin.website-admin.contact.form.view", + "admin.website-admin.contact.form.update", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-analytics.update", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-tags.update", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.google-search-console.update", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.analytics.pixel-meta.update", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.messenger.update", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.whatsapp.update", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.tawk-to.update", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.comunication.twitter.update", + "admin.website-admin.translate.google.view", + "admin.website-admin.translate.google.update", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.faq.update", + "admin.website-admin.content.faq.create", + "admin.website-admin.content.faq.delete", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.gallery.update", + "admin.website-admin.content.gallery.create", + "admin.website-admin.content.gallery.delete", + "admin.website-admin.content.legal.view", + "admin.website-admin.content.legal.update", + "admin.website-admin.content.legal.create", + "admin.website-admin.content.legal.delete", + "admin.website-admin.seo.sitemap.view", + "admin.website-admin.seo.sitemap.update", + "admin.website-admin.seo.jsonld.view", + "admin.website-admin.seo.jsonld.update", + "admin.website-admin.seo.robots.view", + "admin.website-admin.seo.robots.update", + "admin.website-admin.seo.manifest.view", + "admin.website-admin.seo.manifest.update", + "admin.website-admin.seo.canonical.view", + "admin.website-admin.seo.canonical.update", + "admin.website-admin.seo.social-cards.view", + "admin.website-admin.seo.social-cards.update", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.categories.update", + "admin.website-admin.blog.categories.create", + "admin.website-admin.blog.categories.delete", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.tags.update", + "admin.website-admin.blog.tags.create", + "admin.website-admin.blog.tags.delete", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.articles.update", + "admin.website-admin.blog.articles.create", + "admin.website-admin.blog.articles.delete", + "admin.website-admin.blog.comments.view", + "admin.website-admin.blog.comments.update", + "admin.website-admin.blog.comments.create", + "admin.website-admin.blog.comments.delete" + ] + }, + "Admin" : { + "permissions" : [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.general.update", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.social.update", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.settings.indexing.update", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.info.update", + "admin.website-admin.contact.form.view", + "admin.website-admin.contact.form.update", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-analytics.update", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-tags.update", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.google-search-console.update", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.analytics.pixel-meta.update", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.messenger.update", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.whatsapp.update", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.tawk-to.update", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.comunication.twitter.update", + "admin.website-admin.translate.google.view", + "admin.website-admin.translate.google.update", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.faq.update", + "admin.website-admin.content.faq.create", + "admin.website-admin.content.faq.delete", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.gallery.update", + "admin.website-admin.content.gallery.create", + "admin.website-admin.content.gallery.delete", + "admin.website-admin.content.legal.view", + "admin.website-admin.content.legal.update", + "admin.website-admin.content.legal.create", + "admin.website-admin.content.legal.delete", + "admin.website-admin.seo.sitemap.view", + "admin.website-admin.seo.sitemap.update", + "admin.website-admin.seo.jsonld.view", + "admin.website-admin.seo.jsonld.update", + "admin.website-admin.seo.robots.view", + "admin.website-admin.seo.robots.update", + "admin.website-admin.seo.manifest.view", + "admin.website-admin.seo.manifest.update", + "admin.website-admin.seo.canonical.view", + "admin.website-admin.seo.canonical.update", + "admin.website-admin.seo.social-cards.view", + "admin.website-admin.seo.social-cards.update", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.categories.update", + "admin.website-admin.blog.categories.create", + "admin.website-admin.blog.categories.delete", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.tags.update", + "admin.website-admin.blog.tags.create", + "admin.website-admin.blog.tags.delete", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.articles.update", + "admin.website-admin.blog.articles.create", + "admin.website-admin.blog.articles.delete", + "admin.website-admin.blog.comments.view", + "admin.website-admin.blog.comments.update", + "admin.website-admin.blog.comments.create", + "admin.website-admin.blog.comments.delete" + ] + }, + "Auditor" : { + "permissions" : [ + "admin.website-admin.settings.general.view", + "admin.website-admin.settings.social.view", + "admin.website-admin.settings.indexing.view", + "admin.website-admin.contact.info.view", + "admin.website-admin.contact.form.view", + "admin.website-admin.analytics.google-analytics.view", + "admin.website-admin.analytics.google-tags.view", + "admin.website-admin.analytics.google-search-console.view", + "admin.website-admin.analytics.pixel-meta.view", + "admin.website-admin.comunication.messenger.view", + "admin.website-admin.comunication.whatsapp.view", + "admin.website-admin.comunication.tawk-to.view", + "admin.website-admin.comunication.twitter.view", + "admin.website-admin.translate.google.view", + "admin.website-admin.content.faq.view", + "admin.website-admin.content.gallery.view", + "admin.website-admin.content.legal.view", + "admin.website-admin.seo.sitemap.view", + "admin.website-admin.seo.jsonld.view", + "admin.website-admin.seo.robots.view", + "admin.website-admin.seo.manifest.view", + "admin.website-admin.seo.canonical.view", + "admin.website-admin.seo.social-cards.view", + "admin.website-admin.blog.categories.view", + "admin.website-admin.blog.tags.view", + "admin.website-admin.blog.articles.view", + "admin.website-admin.blog.comments.view" + ] + } +} diff --git a/resources/js/chat-settings-card.js b/resources/js/chat-settings-card.js index 5d5bb40..4f7132d 100644 --- a/resources/js/chat-settings-card.js +++ b/resources/js/chat-settings-card.js @@ -1,6 +1,6 @@ -import '@vuexy-admin/notifications/LivewireNotification.js'; -import FormCustomListener from '@vuexy-admin/forms/formCustomListener'; -import registerLivewireHookOnce from '@vuexy-admin/livewire/registerLivewireHookOnce'; +//import '@vuexy-admin/assets/js/notifications/LivewireNotification.js'; +import FormCustomListener from '@vuexy-admin/assets/js/forms/formCustomListener'; +//import registerLivewireHookOnce from '@vuexy-admin/assets/js/livewire/registerLivewireHookOnce'; // Inicializar formularios de ajustes de chat window.ChatSettingsForm = new FormCustomListener({ diff --git a/resources/js/contact-form-settings-card.js b/resources/js/contact-form-settings-card.js index f3b69f0..853f884 100644 --- a/resources/js/contact-form-settings-card.js +++ b/resources/js/contact-form-settings-card.js @@ -1,10 +1,10 @@ -import '@vuexy-admin/notifications/LivewireNotification.js'; -import FormCustomListener from '@vuexy-admin/forms/formCustomListener'; -import registerLivewireHookOnce from '@vuexy-admin/livewire/registerLivewireHookOnce'; +//import '@vuexy-admin/assets/js/notifications/LivewireNotification.js'; +import FormCustomListener from '@vuexy-admin/assets/js/forms/formCustomListener'; +//import registerLivewireHookOnce from '@vuexy-admin/assets/js/livewire/registerLivewireHookOnce'; // Inicializar formularios de ajustes de Formularios de contacto window.ContactFormSettingsForm = new FormCustomListener({ - formSelector: '#website-contact-form-settings-card', + formSelector: '#website-contact-form-card-card', buttonSelectors: ['.btn-save', '.btn-cancel'], callbacks: [() => {}], dispatchOnSubmit: 'save', @@ -81,6 +81,6 @@ window.ContactFormSettingsForm = new FormCustomListener({ } }); -registerLivewireHookOnce('morphed', 'vuexy-website-admin::contact-form-settings', (component) => { +registerLivewireHookOnce('morphed', 'vuexy-website-admin::contact-form-card', (component) => { ContactFormSettingsForm.reloadValidation(); -}); \ No newline at end of file +}); diff --git a/resources/js/contact-info-settings-card.js b/resources/js/contact-info-settings-card.js index 9c8b8e2..b71dfa1 100644 --- a/resources/js/contact-info-settings-card.js +++ b/resources/js/contact-info-settings-card.js @@ -1,10 +1,10 @@ -import '@vuexy-admin/notifications/LivewireNotification.js'; -import FormCustomListener from '@vuexy-admin/forms/formCustomListener'; -import registerLivewireHookOnce from '@vuexy-admin/livewire/registerLivewireHookOnce'; +//import '@vuexy-admin/assets/js/notifications/LivewireNotification.js'; +import FormCustomListener from '@vuexy-admin/assets/js/forms/formCustomListener'; +//import registerLivewireHookOnce from '@vuexy-admin/assets/js/livewire/registerLivewireHookOnce'; // Inicializar formularios de ajustes de información de contacto window.ContactInfoSettingsForm = new FormCustomListener({ - formSelector: '#website-contact-info-settings-card', + formSelector: '#website-contact-info-card-card', buttonSelectors: ['.btn-save', '.btn-cancel'], callbacks: [() => {}], dispatchOnSubmit: 'save', @@ -130,13 +130,13 @@ window.ContactInfoSettingsForm = new FormCustomListener({ } }); -registerLivewireHookOnce('morphed', 'vuexy-website-admin::contact-info-settings', (component) => { +registerLivewireHookOnce('morphed', 'vuexy-website-admin::contact-info-card', (component) => { ContactInfoSettingsForm.reloadValidation(); }); // Inicializar formularios de ajustes de ubicación window.LocationSettingsForm = new FormCustomListener({ - formSelector: '#website-location-settings-card', + formSelector: '#website-location-card-card', buttonSelectors: ['.btn-save', '.btn-cancel'], callbacks: [() => {}], dispatchOnSubmit: 'save', @@ -208,6 +208,6 @@ window.LocationSettingsForm = new FormCustomListener({ } }); -registerLivewireHookOnce('morphed', 'vuexy-website-admin::location-settings', (component) => { +registerLivewireHookOnce('morphed', 'vuexy-website-admin::location-card', (component) => { LocationSettingsForm.reloadValidation(); }); diff --git a/resources/js/google-analytics-settings-card.js b/resources/js/google-analytics-settings-card.js index 80ef540..2b76bf0 100644 --- a/resources/js/google-analytics-settings-card.js +++ b/resources/js/google-analytics-settings-card.js @@ -1,6 +1,6 @@ -import '@vuexy-admin/notifications/LivewireNotification.js'; -import FormCustomListener from '@vuexy-admin/forms/formCustomListener'; -import registerLivewireHookOnce from '@vuexy-admin/livewire/registerLivewireHookOnce'; +//import '@vuexy-admin/assets/js/notifications/LivewireNotification.js'; +import FormCustomListener from '@vuexy-admin/assets/js/forms/formCustomListener'; +//import registerLivewireHookOnce from '@vuexy-admin/assets/js/livewire/registerLivewireHookOnce'; // Inicializar formularios de ajustes de análisis de datos window.AnalyticsSettingsForm = new FormCustomListener({ @@ -38,4 +38,4 @@ window.AnalyticsSettingsForm = new FormCustomListener({ registerLivewireHookOnce('morphed', 'vuexy-website-admin::analytics-index', (component) => { AnalyticsSettingsForm.reloadValidation(); -}); \ No newline at end of file +}); diff --git a/resources/js/website-settings-card.js b/resources/js/website-settings-card.js index 166336e..e3f28c8 100644 --- a/resources/js/website-settings-card.js +++ b/resources/js/website-settings-card.js @@ -1,6 +1,6 @@ -import '@vuexy-admin/notifications/LivewireNotification.js'; -import FormCustomListener from '@vuexy-admin/forms/formCustomListener'; -import registerLivewireHookOnce from '@vuexy-admin/livewire/registerLivewireHookOnce'; +//import '@vuexy-admin/assets/js/notifications/LivewireNotification.js'; +import FormCustomListener from '@vuexy-admin/assets/js/forms/formCustomListener'; +//import registerLivewireHookOnce from '@vuexy-admin/assets/js/livewire/registerLivewireHookOnce'; // Inicializar formularios de ajustes de social media window.SocialSettingsForm = new FormCustomListener({ @@ -128,6 +128,6 @@ window.SocialSettingsForm = new FormCustomListener({ } }); -registerLivewireHookOnce('morphed', 'vuexy-website-admin::social-media-settings', (component) => { +registerLivewireHookOnce('morphed', 'vuexy-website-admin::social-card', (component) => { SocialSettingsForm.reloadValidation(); }); diff --git a/resources/views/google-analytics/index.blade.php b/resources/views/analytics/google-analytics/index.blade.php similarity index 88% rename from resources/views/google-analytics/index.blade.php rename to resources/views/analytics/google-analytics/index.blade.php index 5010e3e..cad4379 100644 --- a/resources/views/google-analytics/index.blade.php +++ b/resources/views/analytics/google-analytics/index.blade.php @@ -17,13 +17,13 @@ @endsection @push('page-script') - @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/google-analytics-settings-card.js') + @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/google-analytics-card-card.js') @endpush @section('content')
- @livewire('vuexy-website-admin::google-analytics-settings') + @livewire('vuexy-website-admin::google-analytics-card')
@endsection diff --git a/resources/views/analytics/google-search-console/index.blade.php b/resources/views/analytics/google-search-console/index.blade.php new file mode 100644 index 0000000..494ea77 --- /dev/null +++ b/resources/views/analytics/google-search-console/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Google Search Console') + +@section('content') +
+
+ @livewire('vuexy-website-admin::google-search-console-card') +
+
+@endsection diff --git a/resources/views/analytics/google-tags/index.blade.php b/resources/views/analytics/google-tags/index.blade.php new file mode 100644 index 0000000..60e0d63 --- /dev/null +++ b/resources/views/analytics/google-tags/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Google Tags') + +@section('content') +
+
+ @livewire('vuexy-website-admin::google-tags-card') +
+
+@endsection diff --git a/resources/views/analytics/pixel-meta/index.blade.php b/resources/views/analytics/pixel-meta/index.blade.php new file mode 100644 index 0000000..104dab1 --- /dev/null +++ b/resources/views/analytics/pixel-meta/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Pixel Meta') + +@section('content') +
+
+ @livewire('vuexy-website-admin::pixel-meta-card') +
+
+@endsection diff --git a/resources/views/blog/article/index.blade.php b/resources/views/blog/article/index.blade.php new file mode 100644 index 0000000..ff077f3 --- /dev/null +++ b/resources/views/blog/article/index.blade.php @@ -0,0 +1,31 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Artículos del Blog') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@section('vendor-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('vuexy-website-admin::blog-articles-table') + +@endsection diff --git a/resources/views/blog/category/index.blade.php b/resources/views/blog/category/index.blade.php new file mode 100644 index 0000000..c094933 --- /dev/null +++ b/resources/views/blog/category/index.blade.php @@ -0,0 +1,31 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Categorías del Blog') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@section('vendor-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('vuexy-website-admin::blog-categories-table') + +@endsection diff --git a/resources/views/blog/comment/index.blade.php b/resources/views/blog/comment/index.blade.php new file mode 100644 index 0000000..3b6d4bc --- /dev/null +++ b/resources/views/blog/comment/index.blade.php @@ -0,0 +1,31 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Comentarios del Blog') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@section('vendor-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('vuexy-website-admin::blog-comments-table') + +@endsection diff --git a/resources/views/blog/tag/index.blade.php b/resources/views/blog/tag/index.blade.php new file mode 100644 index 0000000..ee4f89b --- /dev/null +++ b/resources/views/blog/tag/index.blade.php @@ -0,0 +1,31 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Etiquetas del Blog') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@section('vendor-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('vuexy-website-admin::blog-tags-table') + +@endsection diff --git a/resources/views/comunication/messenger/index.blade.php b/resources/views/comunication/messenger/index.blade.php new file mode 100644 index 0000000..4b2963f --- /dev/null +++ b/resources/views/comunication/messenger/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Facebook Messenger') + +@section('content') +
+
+ @livewire('vuexy-website-admin::messenger-card') +
+
+@endsection diff --git a/resources/views/comunication/tawk-to/index.blade.php b/resources/views/comunication/tawk-to/index.blade.php new file mode 100644 index 0000000..ed31ab8 --- /dev/null +++ b/resources/views/comunication/tawk-to/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Tawk-to Chat') + +@section('content') +
+
+ @livewire('vuexy-website-admin::tawk-to-card') +
+
+@endsection diff --git a/resources/views/social-media/index.blade.php b/resources/views/comunication/twitter/index.blade.php similarity index 77% rename from resources/views/social-media/index.blade.php rename to resources/views/comunication/twitter/index.blade.php index de1ffcf..22c580d 100644 --- a/resources/views/social-media/index.blade.php +++ b/resources/views/comunication/twitter/index.blade.php @@ -1,6 +1,6 @@ @extends('vuexy-admin::layouts.vuexy.layoutMaster') -@section('title', 'Redes Sociales') +@section('title', 'Twitter API') @section('vendor-style') @vite([ @@ -17,9 +17,13 @@ @endsection @push('page-script') - @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/website-settings-card.js') + @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/google-analytics-card-card.js') @endpush @section('content') - @livewire('vuexy-website-admin::social-media-settings') +
+
+ @livewire('vuexy-website-admin::twitter-card') +
+
@endsection diff --git a/resources/views/chat/index.blade.php b/resources/views/comunication/whatsapp/index.blade.php similarity index 89% rename from resources/views/chat/index.blade.php rename to resources/views/comunication/whatsapp/index.blade.php index 4fd72a0..19e2f03 100644 --- a/resources/views/chat/index.blade.php +++ b/resources/views/comunication/whatsapp/index.blade.php @@ -1,6 +1,6 @@ @extends('vuexy-admin::layouts.vuexy.layoutMaster') -@section('title', 'Chat') +@section('title', 'Whatsapp Chat') @section('vendor-style') @vite([ @@ -23,7 +23,7 @@ @section('content')
- @livewire('vuexy-website-admin::chat-settings') + @livewire('vuexy-website-admin::whatsapp-card')
@endsection diff --git a/resources/views/contact-form/index.blade.php b/resources/views/contact/form/index.blade.php similarity index 89% rename from resources/views/contact-form/index.blade.php rename to resources/views/contact/form/index.blade.php index 20bb15f..dd71de6 100644 --- a/resources/views/contact-form/index.blade.php +++ b/resources/views/contact/form/index.blade.php @@ -17,13 +17,13 @@ @endsection @push('page-script') - @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/contact-form-settings-card.js') + @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/contact-form-card-card.js') @endpush @section('content')
- @livewire('vuexy-website-admin::contact-form-settings') + @livewire('vuexy-website-admin::contact-form-card')
@endsection diff --git a/resources/views/contact-info/index.blade.php b/resources/views/contact/info/index.blade.php similarity index 84% rename from resources/views/contact-info/index.blade.php rename to resources/views/contact/info/index.blade.php index c927cc2..2c5b1c0 100644 --- a/resources/views/contact-info/index.blade.php +++ b/resources/views/contact/info/index.blade.php @@ -17,16 +17,16 @@ @endsection @push('page-script') - @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/contact-info-settings-card.js') + @vite('vendor/koneko/laravel-vuexy-website-admin/resources/js/contact-info-card-card.js') @endpush @section('content')
- @livewire('vuexy-website-admin::contact-info-settings') + @livewire('vuexy-website-admin::contact-info-card')
- @livewire('vuexy-website-admin::location-settings') + @livewire('vuexy-website-admin::contact-location-card')
@endsection diff --git a/resources/views/faq/index.blade.php b/resources/views/content/faq/index.blade.php similarity index 100% rename from resources/views/faq/index.blade.php rename to resources/views/content/faq/index.blade.php diff --git a/resources/views/images/index.blade.php b/resources/views/content/gallery/index.blade.php similarity index 82% rename from resources/views/images/index.blade.php rename to resources/views/content/gallery/index.blade.php index 4a81c63..b499ba6 100644 --- a/resources/views/images/index.blade.php +++ b/resources/views/content/gallery/index.blade.php @@ -7,5 +7,5 @@ @endpush @section('content') - @livewire('vuexy-website-admin::images-index') + @livewire('vuexy-website-admin::gallery-index') @endsection diff --git a/resources/views/legal-notices/index.blade.php b/resources/views/content/legal/index.blade.php similarity index 67% rename from resources/views/legal-notices/index.blade.php rename to resources/views/content/legal/index.blade.php index 7cadcbe..5e7fc29 100644 --- a/resources/views/legal-notices/index.blade.php +++ b/resources/views/content/legal/index.blade.php @@ -3,5 +3,5 @@ @section('title', 'Avisos Legales') @section('content') - @livewire('vuexy-website-admin::legal-notices-index') + @livewire('vuexy-website-admin::legal-index') @endsection diff --git a/resources/views/legal-notices/legal-index.blade.php b/resources/views/content/legal/legal-index.blade.php similarity index 83% rename from resources/views/legal-notices/legal-index.blade.php rename to resources/views/content/legal/legal-index.blade.php index ceeef63..338179c 100644 --- a/resources/views/legal-notices/legal-index.blade.php +++ b/resources/views/content/legal/legal-index.blade.php @@ -20,9 +20,11 @@ @endsection @section('page-script') - @vite('modules/Admin/Resources/js/website-settings/legal-settings-scripts.js') + @vite([ + //'modules/Admin/Resources/js/website-settings/legal-settings-scripts.js' + ]) @endsection @section('content') - @livewire('website-legal-settings') + @livewire('legal-index') @endsection diff --git a/resources/views/layouts/base/header.blade.php b/resources/views/layouts/base/header.blade.php new file mode 100644 index 0000000..7e504dc --- /dev/null +++ b/resources/views/layouts/base/header.blade.php @@ -0,0 +1,72 @@ +{{-- SEO HEAD - Generado automáticamente por SeoProfileService --}} +@php($_seo = $_seo ?? []) + +{{-- Canonical + Fonts --}} + + + + +{{-- Charset (el viewport va en layout, no en SEO) --}} + + +{{-- hreflang alternates (multi-language SEO) --}} +@foreach ($_seo['hreflangs'] ?? [] as $lang => $url) + +@endforeach + +{{-- Título + SEO Meta --}} +{{ $_seo['title'] ?? config('app.name') }} + + + + + + + + + +{{-- CSRF (Laravel) --}} + + +{{-- OpenGraph --}} + + + + + + + +{{-- Twitter Card --}} + + + + + + + +{{-- Favicons dinámicos --}} +@foreach ($_seo['favicon'] ?? [] as $size => $path) + @php($fullPath = Str::startsWith($path, ['http://', 'https://']) ? $path : asset('storage/' . $path)) + @switch(true) + @case(Str::endsWith($path, '.svg')) + + @break + @case(Str::endsWith($path, '.apng')) + + @break + @case(Str::startsWith($size, 'apple')) + + @break + @default + + @endswitch +@endforeach + +{{-- Web App Manifest --}} + + + +{{-- JSON-LD Structured Data --}} +@if (!empty($_seo['ld+json'])) + +@endif diff --git a/resources/views/livewire/vuexy/analytics-settings.blade.php b/resources/views/livewire/analytics/google-analytics/google-analytics-card.blade.php similarity index 100% rename from resources/views/livewire/vuexy/analytics-settings.blade.php rename to resources/views/livewire/analytics/google-analytics/google-analytics-card.blade.php diff --git a/resources/views/livewire/analytics/google-search-console/google-search-console-card.blade.php b/resources/views/livewire/analytics/google-search-console/google-search-console-card.blade.php new file mode 100644 index 0000000..f9e2067 --- /dev/null +++ b/resources/views/livewire/analytics/google-search-console/google-search-console-card.blade.php @@ -0,0 +1,34 @@ +
+ + + + + + +
+
+ + +
+
+
+
+
diff --git a/resources/views/livewire/analytics/google-tags/google-tags-card.blade.php b/resources/views/livewire/analytics/google-tags/google-tags-card.blade.php new file mode 100644 index 0000000..f9e2067 --- /dev/null +++ b/resources/views/livewire/analytics/google-tags/google-tags-card.blade.php @@ -0,0 +1,34 @@ +
+ + + + + + +
+
+ + +
+
+
+
+
diff --git a/resources/views/livewire/analytics/pixel-meta/pixel-meta-card.blade.php b/resources/views/livewire/analytics/pixel-meta/pixel-meta-card.blade.php new file mode 100644 index 0000000..f9e2067 --- /dev/null +++ b/resources/views/livewire/analytics/pixel-meta/pixel-meta-card.blade.php @@ -0,0 +1,34 @@ +
+ + + + + + +
+
+ + +
+
+
+
+
diff --git a/resources/views/livewire/faq/index.blade.php b/resources/views/livewire/blog/article/index.blade.php similarity index 100% rename from resources/views/livewire/faq/index.blade.php rename to resources/views/livewire/blog/article/index.blade.php diff --git a/resources/views/livewire/blog/category/index.blade.php b/resources/views/livewire/blog/category/index.blade.php new file mode 100644 index 0000000..12a6a21 --- /dev/null +++ b/resources/views/livewire/blog/category/index.blade.php @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/resources/views/livewire/blog/comment/index.blade.php b/resources/views/livewire/blog/comment/index.blade.php new file mode 100644 index 0000000..12a6a21 --- /dev/null +++ b/resources/views/livewire/blog/comment/index.blade.php @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/resources/views/livewire/blog/tag/index.blade.php b/resources/views/livewire/blog/tag/index.blade.php new file mode 100644 index 0000000..12a6a21 --- /dev/null +++ b/resources/views/livewire/blog/tag/index.blade.php @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/resources/views/livewire/vuexy/chat-settings.blade.php b/resources/views/livewire/comunication/messenger/messenger-card.blade.php similarity index 100% rename from resources/views/livewire/vuexy/chat-settings.blade.php rename to resources/views/livewire/comunication/messenger/messenger-card.blade.php diff --git a/resources/views/livewire/comunication/tawk-to/tawk-to-card.blade.php b/resources/views/livewire/comunication/tawk-to/tawk-to-card.blade.php new file mode 100644 index 0000000..d202a7d --- /dev/null +++ b/resources/views/livewire/comunication/tawk-to/tawk-to-card.blade.php @@ -0,0 +1,58 @@ +
+ + + {{-- Proveedor --}} +
+ + +
+ + {{-- Configuración de WhatsApp --}} +
+
WhatsApp
+ + + +
+
+ + {{-- Botones de acción --}} +
+
+ + + +
+
+ + {{-- Contenedor para notificaciones --}} +
+
+
diff --git a/resources/views/livewire/comunication/twitter/twitter-card.blade.php b/resources/views/livewire/comunication/twitter/twitter-card.blade.php new file mode 100644 index 0000000..f9e2067 --- /dev/null +++ b/resources/views/livewire/comunication/twitter/twitter-card.blade.php @@ -0,0 +1,34 @@ +
+ + + + + + +
+
+ + +
+
+
+
+
diff --git a/resources/views/livewire/comunication/whatsapp/whatsapp-card.blade.php b/resources/views/livewire/comunication/whatsapp/whatsapp-card.blade.php new file mode 100644 index 0000000..d202a7d --- /dev/null +++ b/resources/views/livewire/comunication/whatsapp/whatsapp-card.blade.php @@ -0,0 +1,58 @@ +
+ + + {{-- Proveedor --}} +
+ + +
+ + {{-- Configuración de WhatsApp --}} +
+
WhatsApp
+ + + +
+
+ + {{-- Botones de acción --}} +
+
+ + + +
+
+ + {{-- Contenedor para notificaciones --}} +
+
+
diff --git a/resources/views/livewire/vuexy/contact-form-settings.blade.php b/resources/views/livewire/contact/form/contact-form-card.blade.php similarity index 93% rename from resources/views/livewire/vuexy/contact-form-settings.blade.php rename to resources/views/livewire/contact/form/contact-form-card.blade.php index 9a3edf4..bcbb6a7 100644 --- a/resources/views/livewire/vuexy/contact-form-settings.blade.php +++ b/resources/views/livewire/contact/form/contact-form-card.blade.php @@ -1,5 +1,5 @@
- + diff --git a/resources/views/livewire/vuexy/contact-info-settings.blade.php b/resources/views/livewire/contact/info/contact-info-card.blade.php similarity index 94% rename from resources/views/livewire/vuexy/contact-info-settings.blade.php rename to resources/views/livewire/contact/info/contact-info-card.blade.php index 25a6cc6..13a6ba9 100644 --- a/resources/views/livewire/vuexy/contact-info-settings.blade.php +++ b/resources/views/livewire/contact/info/contact-info-card.blade.php @@ -1,5 +1,5 @@
- +
diff --git a/resources/views/livewire/vuexy/location-settings.blade.php b/resources/views/livewire/contact/info/location-card.blade.php similarity index 92% rename from resources/views/livewire/vuexy/location-settings.blade.php rename to resources/views/livewire/contact/info/location-card.blade.php index 6aeb34d..f0ce50a 100644 --- a/resources/views/livewire/vuexy/location-settings.blade.php +++ b/resources/views/livewire/contact/info/location-card.blade.php @@ -1,5 +1,5 @@
- +
diff --git a/resources/views/livewire/content/faq/index.blade.php b/resources/views/livewire/content/faq/index.blade.php new file mode 100644 index 0000000..12a6a21 --- /dev/null +++ b/resources/views/livewire/content/faq/index.blade.php @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/resources/views/livewire/images/index.blade.php b/resources/views/livewire/content/gallery/index.blade.php similarity index 100% rename from resources/views/livewire/images/index.blade.php rename to resources/views/livewire/content/gallery/index.blade.php diff --git a/resources/views/livewire/legal-notices/index.blade.php b/resources/views/livewire/content/legal-notices/index.blade.php similarity index 100% rename from resources/views/livewire/legal-notices/index.blade.php rename to resources/views/livewire/content/legal-notices/index.blade.php diff --git a/resources/views/livewire/sitemap-manager/index.blade.php b/resources/views/livewire/seo/canonical/index.blade.php similarity index 100% rename from resources/views/livewire/sitemap-manager/index.blade.php rename to resources/views/livewire/seo/canonical/index.blade.php diff --git a/resources/views/livewire/seo/jsonld/index.blade.php b/resources/views/livewire/seo/jsonld/index.blade.php new file mode 100644 index 0000000..69e3e8c --- /dev/null +++ b/resources/views/livewire/seo/jsonld/index.blade.php @@ -0,0 +1,22 @@ +
+

Gestión del Sitemap

+ + + + + + +
    + @foreach($urls as $url) +
  • {{ $url->url }} ({{ $url->changefreq }}, {{ $url->priority }}) + +
  • + @endforeach +
+ + +
diff --git a/resources/views/livewire/seo/manifest/manifest-card.blade.php b/resources/views/livewire/seo/manifest/manifest-card.blade.php new file mode 100644 index 0000000..69e3e8c --- /dev/null +++ b/resources/views/livewire/seo/manifest/manifest-card.blade.php @@ -0,0 +1,22 @@ +
+

Gestión del Sitemap

+ + + + + + +
    + @foreach($urls as $url) +
  • {{ $url->url }} ({{ $url->changefreq }}, {{ $url->priority }}) + +
  • + @endforeach +
+ + +
diff --git a/resources/views/livewire/seo/robots/robot-card.blade.php b/resources/views/livewire/seo/robots/robot-card.blade.php new file mode 100644 index 0000000..69e3e8c --- /dev/null +++ b/resources/views/livewire/seo/robots/robot-card.blade.php @@ -0,0 +1,22 @@ +
+

Gestión del Sitemap

+ + + + + + +
    + @foreach($urls as $url) +
  • {{ $url->url }} ({{ $url->changefreq }}, {{ $url->priority }}) + +
  • + @endforeach +
+ + +
diff --git a/resources/views/livewire/seo/sitemap/UrlOffcanvas-form.blade.php b/resources/views/livewire/seo/sitemap/UrlOffcanvas-form.blade.php new file mode 100644 index 0000000..31328b1 --- /dev/null +++ b/resources/views/livewire/seo/sitemap/UrlOffcanvas-form.blade.php @@ -0,0 +1,39 @@ +
+ + + + + + {{-- Usuario --}} + + + {{-- Correos electrónicos --}} + + + {{-- Contraseña --}} + + + +
+ +
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/seo/sitemap/index.blade.php b/resources/views/livewire/seo/sitemap/index.blade.php new file mode 100644 index 0000000..69e3e8c --- /dev/null +++ b/resources/views/livewire/seo/sitemap/index.blade.php @@ -0,0 +1,22 @@ +
+

Gestión del Sitemap

+ + + + + + +
    + @foreach($urls as $url) +
  • {{ $url->url }} ({{ $url->changefreq }}, {{ $url->priority }}) + +
  • + @endforeach +
+ + +
diff --git a/resources/views/livewire/seo/social-cards/index.blade.php b/resources/views/livewire/seo/social-cards/index.blade.php new file mode 100644 index 0000000..69e3e8c --- /dev/null +++ b/resources/views/livewire/seo/social-cards/index.blade.php @@ -0,0 +1,22 @@ +
+

Gestión del Sitemap

+ + + + + + +
    + @foreach($urls as $url) +
  • {{ $url->url }} ({{ $url->changefreq }}, {{ $url->priority }}) + +
  • + @endforeach +
+ + +
diff --git a/resources/views/livewire/vuexy/template-settings.blade.php b/resources/views/livewire/settings/general/___template-card.blade.php similarity index 100% rename from resources/views/livewire/vuexy/template-settings.blade.php rename to resources/views/livewire/settings/general/___template-card.blade.php diff --git a/resources/views/livewire/vuexy/logo-on-dark-bg-settings.blade.php b/resources/views/livewire/settings/general/logo-on-dark-bg-card.blade.php similarity index 96% rename from resources/views/livewire/vuexy/logo-on-dark-bg-settings.blade.php rename to resources/views/livewire/settings/general/logo-on-dark-bg-card.blade.php index dd08120..1ff0762 100644 --- a/resources/views/livewire/vuexy/logo-on-dark-bg-settings.blade.php +++ b/resources/views/livewire/settings/general/logo-on-dark-bg-card.blade.php @@ -1,5 +1,5 @@
-
+
-
+
-
+
-
diff --git a/resources/views/livewire/vuexy/website-favicon-settings.blade.php b/resources/views/livewire/settings/general/website-favicon-card.blade.php similarity index 98% rename from resources/views/livewire/vuexy/website-favicon-settings.blade.php rename to resources/views/livewire/settings/general/website-favicon-card.blade.php index 3705dd5..755b777 100644 --- a/resources/views/livewire/vuexy/website-favicon-settings.blade.php +++ b/resources/views/livewire/settings/general/website-favicon-card.blade.php @@ -1,5 +1,5 @@
-
+
+

Visibilidad en buscadores

+ +
diff --git a/resources/views/livewire/vuexy/social-media-settings.blade.php b/resources/views/livewire/settings/social/social-card.blade.php similarity index 100% rename from resources/views/livewire/vuexy/social-media-settings.blade.php rename to resources/views/livewire/settings/social/social-card.blade.php diff --git a/resources/views/livewire/translate/google/google-translate-card.blade.php b/resources/views/livewire/translate/google/google-translate-card.blade.php new file mode 100644 index 0000000..d202a7d --- /dev/null +++ b/resources/views/livewire/translate/google/google-translate-card.blade.php @@ -0,0 +1,58 @@ +
+ + + {{-- Proveedor --}} +
+ + +
+ + {{-- Configuración de WhatsApp --}} +
+
WhatsApp
+ + + +
+
+ + {{-- Botones de acción --}} +
+
+ + + +
+
+ + {{-- Contenedor para notificaciones --}} +
+
+
diff --git a/resources/views/seo/canonical/index.blade.php b/resources/views/seo/canonical/index.blade.php new file mode 100644 index 0000000..e68aaf4 --- /dev/null +++ b/resources/views/seo/canonical/index.blade.php @@ -0,0 +1,7 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Canonical URLs') + +@section('content') + @livewire('vuexy-website-admin::canonical-index') +@endsection diff --git a/resources/views/seo/jsonld/index.blade.php b/resources/views/seo/jsonld/index.blade.php new file mode 100644 index 0000000..6cc7653 --- /dev/null +++ b/resources/views/seo/jsonld/index.blade.php @@ -0,0 +1,7 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Google JSON-LD') + +@section('content') + @livewire('vuexy-website-admin::jsonld-index') +@endsection diff --git a/resources/views/seo/manifest/index.blade.php b/resources/views/seo/manifest/index.blade.php new file mode 100644 index 0000000..4bebb31 --- /dev/null +++ b/resources/views/seo/manifest/index.blade.php @@ -0,0 +1,7 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'manifest.js') + +@section('content') + @livewire('vuexy-website-admin::manifest-card') +@endsection diff --git a/resources/views/seo/robots/index.blade.php b/resources/views/seo/robots/index.blade.php new file mode 100644 index 0000000..4880701 --- /dev/null +++ b/resources/views/seo/robots/index.blade.php @@ -0,0 +1,7 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Robots.txt') + +@section('content') + @livewire('vuexy-website-admin::robots-card') +@endsection diff --git a/resources/views/sitemap-manager/index.blade.php b/resources/views/seo/sitemap/index.blade.php similarity index 67% rename from resources/views/sitemap-manager/index.blade.php rename to resources/views/seo/sitemap/index.blade.php index 0ed2b83..8ff6a4e 100644 --- a/resources/views/sitemap-manager/index.blade.php +++ b/resources/views/seo/sitemap/index.blade.php @@ -3,5 +3,5 @@ @section('title', 'Mapa del Sitio') @section('content') - @livewire('vuexy-website-admin::sitemap-manager-index') + @livewire('vuexy-website-admin::sitemap-index') @endsection diff --git a/resources/views/seo/social-cards/index.blade.php b/resources/views/seo/social-cards/index.blade.php new file mode 100644 index 0000000..f2d2c0e --- /dev/null +++ b/resources/views/seo/social-cards/index.blade.php @@ -0,0 +1,7 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Preview Social Cards') + +@section('content') + @livewire('vuexy-website-admin::social-cards-index') +@endsection diff --git a/resources/views/general-settings/index.blade.php b/resources/views/settings/general/index.blade.php similarity index 66% rename from resources/views/general-settings/index.blade.php rename to resources/views/settings/general/index.blade.php index f843973..23c0de2 100644 --- a/resources/views/general-settings/index.blade.php +++ b/resources/views/settings/general/index.blade.php @@ -9,12 +9,12 @@ @section('content')
- @livewire('vuexy-website-admin::website-description-settings') - @livewire('vuexy-website-admin::website-favicon-settings') + @livewire('vuexy-website-admin::website-description-card') + @livewire('vuexy-website-admin::website-favicon-card')
- @livewire('vuexy-website-admin::logo-on-light-bg-settings') - @livewire('vuexy-website-admin::logo-on-dark-bg-settings') + @livewire('vuexy-website-admin::logo-on-light-bg-card') + @livewire('vuexy-website-admin::logo-on-dark-bg-card')
@endsection diff --git a/resources/views/settings/indexing/index.blade.php b/resources/views/settings/indexing/index.blade.php new file mode 100644 index 0000000..96e3d26 --- /dev/null +++ b/resources/views/settings/indexing/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Indexación') + +@section('content') +
+
+ @livewire('vuexy-website-admin::indexing-card') +
+
+@endsection diff --git a/resources/views/settings/social/index.blade.php b/resources/views/settings/social/index.blade.php new file mode 100644 index 0000000..eb05070 --- /dev/null +++ b/resources/views/settings/social/index.blade.php @@ -0,0 +1,27 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Enlaces de redes sociales') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/form-validation.scss' + ]) +@endsection + +@section('vendor-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/popular.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/bootstrap5.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/auto-focus.js', + ]) +@endsection + +@push('page-script') + @vite([ + //'vendor/koneko/laravel-vuexy-website-admin/resources/js/website-settings-card.js' + ]) +@endpush + +@section('content') + @livewire('vuexy-website-admin::social-card') +@endsection diff --git a/resources/views/translate/google/index.blade.php b/resources/views/translate/google/index.blade.php new file mode 100644 index 0000000..11aded9 --- /dev/null +++ b/resources/views/translate/google/index.blade.php @@ -0,0 +1,11 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Google Translate') + +@section('content') +
+
+ @livewire('vuexy-website-admin::google-tanslate-card') +
+
+@endsection diff --git a/Models/SitemapConfiguration.php b/resources/views/website/blocks/call-to-action.blade.php similarity index 100% rename from Models/SitemapConfiguration.php rename to resources/views/website/blocks/call-to-action.blade.php diff --git a/resources/views/website/blocks/features.blade.php b/resources/views/website/blocks/features.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/website/blocks/hero.blade.php b/resources/views/website/blocks/hero.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/website/templates/default.blade.php b/resources/views/website/templates/default.blade.php new file mode 100644 index 0000000..265a75f --- /dev/null +++ b/resources/views/website/templates/default.blade.php @@ -0,0 +1,540 @@ +@extends('porto::layouts.master') + +@section('vendor-style') + + + + @vite('vendor/koneko/laravel-vuexy-website-layout-porto/resources/assets/css/skins/default.css') +@endsection + +@section('content') + @if(request()->is('preview/*')) +
+ Estás visualizando una vista previa de contenido no publicado. +
+ @endif + + + + + +
+
+ +
+
+

+ The fastest way to grow your business with the leader in Technology + Check out our options and features included. +

+
+ +
+ +
+
+ +
+ +
+
+

+ Porto is + + incredibly + especially + extremely + + beautiful and fully responsive. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce elementum, nulla vel pellentesque consequat, ante nulla hendrerit arcu, ac tincidunt mauris lacus sed leo. +

+
+
+ +
+ +
+
+
+ +
+ + +
+
+ + Strategy +
+
+
+
+ + Planning +
+
+
+
+ + Build +
+
+
+
+
+
    +
  • +
  • +
  • +
+
+ Our Work +
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+

Our Features

+
+
+
+
+ +
+
+

Customer Support

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+ +
+
+

HTML5 / CSS3 / JS

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+ +
+
+

500+ Google Fonts

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+ +
+
+

Colors

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+
+
+ +
+
+

Sliders

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+ +
+
+

Icons

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+ +
+
+

Buttons

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+ +
+
+

Lightbox

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blandit massa

+
+
+
+
+
+
+

and more...

+ +
+
+ +
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus blanorem ipsum dolor sit amet, consecte.

+

Adipiscing elit phasellus blanit ma... read more

+
+
+
+
+ +
+
+

Donec tellus massa, tristique sit amet condimentum vel, facilisis quis sapien.

+
+
+
+
+ +
+
+

Donec tellus massa, tristique sit amet condimentum vel, facilisis quis sapien.

+
+
+
+
+
+
+ +
+ +
+
+

+ We're not the only ones + + excited + happy + + about Porto Template... +

+

50,000 CUSTOMERS IN 100 COUNTRIES USE PORTO TEMPLATE. MEET OUR CUSTOMERS.

+
+
+ +
+ +
+ +
+ +
+ +
+@endsection diff --git a/routes/admin.php b/routes/admin.php deleted file mode 100644 index 739664a..0000000 --- a/routes/admin.php +++ /dev/null @@ -1,59 +0,0 @@ -name('admin.website-admin.')->middleware(['web', 'auth', 'admin'])->group(function () { - // ajustes generales - Route::controller(VuexyWebsiteAdminController::class)->prefix('ajustes-generales')->group(function () { - Route::get('ajustes-generales', 'index')->name('general-settings.index'); - }); - - // Avisos legales - Route::controller(LegalNoticesController::class)->prefix('avisos-legales')->group(function () { - Route::get('/', 'index')->name('legal-notices.index'); - }); - - // Preguntas frecuentes - Route::controller(FaqController::class)->prefix('preguntas-frecuentes')->group(function () { - Route::get('/', 'index')->name('faq.index'); - }); - - // Redes sociales - Route::controller(SocialMediaController::class)->prefix('redes-sociales')->group(function () { - Route::get('/', 'index')->name('social-media.index'); - }); - - // Chat - Route::controller(ChatController::class)->prefix('chat')->group(function () { - Route::get('/', 'index')->name('chat.index'); - }); - - // Galería de imágenes - Route::controller(ImagesController::class)->prefix('galeria-de-imagenes')->group(function () { - Route::get('/', 'index')->name('images.index'); - }); - - // Google Analytics - Route::controller(GoogleAnalyticsController::class)->prefix('google-analytics')->group(function () { - Route::get('/', 'index')->name('google-analytics.index'); - }); - - // Información de contacto - Route::controller(ContactInfoController::class)->prefix('informacion-de-contacto')->group(function () { - Route::get('/', 'index')->name('contact-info.index'); - }); - - // Formulario de contacto - Route::controller(ContactFormController::class)->prefix('formulario-de-contacto')->group(function () { - Route::get('/', 'index')->name('contact-form.index'); - }); - - // Mapa del sitio - Route::controller(SitemapController::class)->prefix('mapa-del-sitio')->group(function () { - Route::get('/', 'index')->name('sitemap.index'); - }); -}); diff --git a/routes/koneko_website_admin.php b/routes/koneko_website_admin.php new file mode 100644 index 0000000..8116c87 --- /dev/null +++ b/routes/koneko_website_admin.php @@ -0,0 +1,64 @@ +route('configuracion-general', 'settings.', SettingsController::class, function () { + Route::get('ajustes-generales', 'generalIndex')->name('general.index'); + Route::get('enlaces-sociales', 'socialIndex')->name('social.index'); + Route::get('visibilidad-en-buscadores', 'indexingIndex')->name('indexing.index'); + }); + + // Web & SEO / Contacto + $r->route('contacto', 'contact.', ContactController::class, function () { + Route::get('informacion-de-contacto', 'infoIndex')->name('info.index'); + Route::get('formulario-de-contacto', 'formIndex')->name('form.index'); + }); + + // Web & SEO / Analítica y seguimiento + $r->route('analitica-y-seguimiento', 'analytics.', AnalyticsController::class, function () { + Route::get('google-analytics', 'googleAnalyticsIndex')->name('google-analytics.index'); + Route::get('google-tags', 'googleTagsIndex')->name('google-tags.index'); + Route::get('google-search-console', 'googleSearchConsoleIndex')->name('google-search-console.index'); + Route::get('pixel-meta', 'pixelMetaIndex')->name('pixel-meta.index'); + }); + + // Web & SEO / Chat & Comunicación + $r->route('chat-y-comunicacion', 'comunication.', ComunicationController::class, function () { + Route::get('facebook-messenger', 'messengerIndex')->name('messenger.index'); + Route::get('whatsapp-chat', 'whatsappIndex')->name('whatsapp.index'); + Route::get('tawk-to', 'tawkToIndex')->name('tawk-to.index'); + Route::get('twitter-api', 'twitterIndex')->name('twitter.index'); + }); + + // Web & SEO / Traducciones e internacional + $r->route('traducciones-e-internacional', 'translate.', TranstaleController::class, function () { + Route::get('google-translate', 'googleIndex')->name('google.index'); + }); + + // Web & SEO / Contenido + $r->route('contenido', 'content.', ContentController::class, function () { + Route::get('preguntas-frecuentes', 'faqIndex')->name('faq.index'); + Route::get('galeria-de-imagenes', 'galleryIndex')->name('gallery.index'); + Route::get('avisos-legales', 'legalIndex')->name('legal.index'); + }); + + // Web & SEO / Herramientas SEO + $r->route('herramientas-seo', 'seo.', SeoController::class, function () { + Route::get('mapa-del-sitio', 'sitemapIndex')->name('sitemap.index'); + Route::get('google-json-ld', 'jsonldIndex')->name('jsonld.index'); + Route::get('robots-txt', 'robotsIndex')->name('robots.index'); + Route::get('manifest-json', 'manifestIndex')->name('manifest.index'); + Route::get('cannonical-urls', 'canonicalIndex')->name('canonical.index'); + Route::get('preview-social-cards', 'socialCardsIndex')->name('social-cards.index'); + }); +}); diff --git a/routes/koneko_website_blog.php b/routes/koneko_website_blog.php new file mode 100644 index 0000000..9f059b8 --- /dev/null +++ b/routes/koneko_website_blog.php @@ -0,0 +1,44 @@ +route('categorias', 'categories.', BlogCategoryController::class, function () { + Route::get('/', 'index')->name('index'); + Route::get('create', 'create')->name('create'); + Route::get('edit/{id}', 'edit')->name('edit'); + Route::get('delete/{id}', 'delete')->name('delete'); + }); + + // Etiquetas + $r->route('etiquetas', 'tags.', BlogTagController::class, function () { + Route::get('/', 'index')->name('index'); + Route::get('create', 'create')->name('create'); + Route::get('edit/{id}', 'edit')->name('edit'); + Route::get('delete/{id}', 'delete')->name('delete'); + }); + + // Artículos + $r->route('articulos', 'articles.', BlogArticleController::class, function () { + Route::get('/', 'index')->name('index'); + Route::get('create', 'create')->name('create'); + Route::get('edit/{id}', 'edit')->name('edit'); + Route::get('delete/{id}', 'delete')->name('delete'); + }); + + // Comentarios + $r->route('comentarios', 'comments.', BlogCommentController::class, function () { + Route::get('/', 'index')->name('index'); + Route::get('create', 'create')->name('create'); + Route::get('edit/{id}', 'edit')->name('edit'); + Route::get('delete/{id}', 'delete')->name('delete'); + }); +}); diff --git a/routes/koneko_website_cms.php b/routes/koneko_website_cms.php new file mode 100644 index 0000000..69180d1 --- /dev/null +++ b/routes/koneko_website_cms.php @@ -0,0 +1,8 @@ +where('slug', '^(?!admin|login|register|logout|email|user|storage|api|livewire|_debugbar|sanctum|preview)(.*)$') + ->name('website.content'); + +// Vista previa con firma +Route::get('/preview/{slug}', [WebsitePageController::class, 'preview']) + ->middleware(['signed']) // Protege con firma + ->name('website.preview'); diff --git a/src/Application/Bootstrap/Context/SiteContext.php b/src/Application/Bootstrap/Context/SiteContext.php new file mode 100644 index 0000000..0d92e24 --- /dev/null +++ b/src/Application/Bootstrap/Context/SiteContext.php @@ -0,0 +1,72 @@ +getHost(); + $path = trim($request->path(), '/'); + + // 🧪 Estrategia 1: dominio exacto + $site = WebsiteSite::where('domain', $host)->first(); + + // 🧪 Estrategia 2: subdominio match (ej. tienda1.koneko.mx) + if (!$site && str_contains($host, '.')) { + $subdomain = explode('.', $host)[0]; + $site = WebsiteSite::where('subdomain', $subdomain)->first(); + } + + // 🧪 Estrategia 3: segmento del path (ej. /site-x/*) + if (!$site && str_contains($path, '/')) { + $firstSegment = explode('/', $path)[0]; + $site = WebsiteSite::where('slug', $firstSegment)->first(); + } + + // Establece contexto (null si no hay match) + static::$resolved = $site; + + return $site; + } + + /** + * Fuerza un sitio específico (desde sesión o entorno controlado) + */ + public static function set(WebsiteSite $site): void + { + static::$resolved = $site; + } + + /** + * Limpia el contexto (en tests o entorno controlado) + */ + public static function forget(): void + { + static::$resolved = null; + } +} diff --git a/src/Application/Cache/Builders/BlogLayoutVarsBuilder.php b/src/Application/Cache/Builders/BlogLayoutVarsBuilder.php new file mode 100644 index 0000000..afdfe74 --- /dev/null +++ b/src/Application/Cache/Builders/BlogLayoutVarsBuilder.php @@ -0,0 +1,17 @@ +ajax()) { + return app(FaqTableConfigBuilder::class) + ->getQueryBuilder($request) + ->getJson(); + } + return view('vuexy-website-admin::content.faq.index'); + } + + public function galleryIndex() + { + return view('vuexy-website-admin::content.gallery.index'); + } + + public function legalIndex() + { + return view('vuexy-website-admin::content.legal.index'); + } +} diff --git a/src/Application/Http/Controllers/SeoController.php b/src/Application/Http/Controllers/SeoController.php new file mode 100644 index 0000000..d1f8278 --- /dev/null +++ b/src/Application/Http/Controllers/SeoController.php @@ -0,0 +1,45 @@ +hasValidSignature()) { + abort(403, 'Firma de vista previa no válida.'); + } + + $template = View::shared('_layout.template') ?? 'anonymous_template'; + $type = View::shared('_layout.type') ?? 'page'; + $view = "{$template}::{$type}"; + + if (!View::exists($view)) { + abort(404, "Plantilla de vista previa no encontrada: {$view}"); + } + + return view($view); + } +} diff --git a/src/Application/Http/Middleware/WebsiteContentMiddleware copy.php b/src/Application/Http/Middleware/WebsiteContentMiddleware copy.php new file mode 100644 index 0000000..d3d157e --- /dev/null +++ b/src/Application/Http/Middleware/WebsiteContentMiddleware copy.php @@ -0,0 +1,65 @@ +route('slug'); + + /** @var WebsiteSite $site */ + $site = app('currentSite'); + + /** @var WebsiteContent|null $content */ + $content = WebsiteContent::query() + ->with(['seoProfile']) + ->where('site_id', $site->id) + ->bySlug($slug) + ->firstOrFail(); + + // 🛑 Bloqueo por estado + if ($content->is_draft && !Auth::check()) { + abort(403, 'Contenido no publicado.'); + } + + // 🛑 Fechas de visibilidad + $now = now(); + if ( + $content->visible_from && $content->visible_from > $now || + $content->visible_until && $content->visible_until < $now + ) { + abort(403, 'Contenido no disponible.'); + } + + // 🔐 Roles o permisos + if (!empty($content->roles) && !Auth::user()?->hasAnyRole($content->roles)) { + abort(403, 'Acceso restringido.'); + } + + if (!empty($content->permissions) && !Auth::user()?->hasAnyPermission($content->permissions)) { + abort(403, 'Permiso insuficiente.'); + } + + // 🧠 SEO + Layout + Vista pública + $seo = $content->getEffectiveSeoMetadata(); + $layout = $content->template ?? $site->template ?? 'vuexy-website-layout-porto'; + $variant = $content->type ?? 'page'; + + // 📤 Compartir variables globales a la vista pública + View::share([ + '_content' => $content, + '_seo' => $seo, + '_template' => $layout, + '_variant' => $variant, + ]); + + return $next($request); + } +} diff --git a/src/Application/Http/Middleware/WebsiteContentMiddleware.php b/src/Application/Http/Middleware/WebsiteContentMiddleware.php new file mode 100644 index 0000000..f1babc4 --- /dev/null +++ b/src/Application/Http/Middleware/WebsiteContentMiddleware.php @@ -0,0 +1,67 @@ +route('slug'); + + + /* + $site = app('currentSite'); + + $content = WebsiteContent::query() + ->with(['seoProfile']) + ->where('site_id', $site->id) + ->bySlug($slug) + ->firstOrFail(); + + if ($request->routeIs('website.preview') && $request->hasValidSignature()) { + View::share('_isPreview', true); + + } else { + if ($content->is_draft && !Auth::check()) { + throw new HttpException(403, 'Contenido no publicado.'); + } + + $now = now(); + if ( + ($content->visible_from && $content->visible_from > $now) || + ($content->visible_until && $content->visible_until < $now) + ) { + throw new HttpException(403, 'Contenido no disponible.'); + } + + $user = Auth::user(); + if (!empty($content->roles) && !$user?->hasAnyRole($content->roles)) { + throw new HttpException(403, 'Acceso restringido.'); + } + + if (!empty($content->permissions) && !$user?->hasAnyPermission($content->permissions)) { + throw new HttpException(403, 'Permiso insuficiente.'); + } + } + + $seo = $content->getEffectiveSeoMetadata(); + $layout = $content->template ?? $site->template ?? 'vuexy-website-layout-porto'; + $variant = $content->type ?? 'page'; + + View::share([ + '_content' => $content, + '_seo' => $seo, + '_template' => $layout, + '_variant' => $variant, + ]); + */ + + return $next($request); + } +} diff --git a/src/Application/Http/Middleware/WebsiteContextMiddleware.php b/src/Application/Http/Middleware/WebsiteContextMiddleware.php new file mode 100644 index 0000000..7ed05da --- /dev/null +++ b/src/Application/Http/Middleware/WebsiteContextMiddleware.php @@ -0,0 +1,94 @@ +header('Accept'), 'text/html')) { + return $next($request); + } + + // Detecta el sitio activo desde dominio/subdominio/path + $site = SiteContext::resolveFromRequest($request); + + $layout = [ + 'template' => $site->template, + 'status' => $site->status, + 'is_indexable' => $site->is_indexable, + ]; + + $seo = [ + 'canonical' => $site->canonical ?? '', + 'viewport' => $site->viewport ?? '', + 'title' => $site->title ?? '', + 'description' => $site->description ?? '', + 'robots' => $site->robots ?? '', + 'language' => $site->language ?? '', + 'author' => $site->author ?? '', + 'og:title' => $site->og_title ?? '', + 'og:site_name' => $site->og_site_name ?? '', + 'og:url' => $site->og_url ?? '', + 'og:description' => $site->og_description ?? '', + 'og:type' => $site->og_type ?? '', + 'og:image' => $site->og_image ?? '', + 'twitter:card' => $site->twitter_card ?? '', + 'twitter:site' => $site->twitter_site ?? '', + 'twitter:creator' => $site->twitter_creator ?? '', + 'favicon' => [ + '180x180' => $site->favicon['180x180'] ?? '', + '152x152' => $site->favicon['152x152'] ?? '', + '120x120' => $site->favicon['120x120'] ?? '', + '76x76' => $site->favicon['76x76'] ?? '', + '192x192' => $site->favicon['192x192'] ?? '', + '16x16' => $site->favicon['16x16'] ?? '', + ], + 'theme-color' => $site->theme_color ?? '', + 'ld+json' => $site->ld_json ?? '', + ]; + + $social = [ + 'twitter:card' => $site->twitter_card ?? '', + 'twitter:site' => $site->twitter_site ?? '', + 'twitter:creator' => $site->twitter_creator ?? '', + ]; + + $contact = [ + 'email' => $site->contact_email ?? '', + 'phone' => $site->contact_phone ?? '', + 'address' => $site->contact_address ?? '', + ]; + + /* + // Variables visuales generales (favicon, logo, layout, etc.) + $layoutVars = app(WebsiteLayoutVarsBuilder::class)->forSite($site)->get(); + + // Variables SEO (title, meta, index, canonical, etc.) + $seoVars = app(WebsiteSeoVarsBuilder::class)->forSite($site)->get(); + + // Variables sociales (twitter, opengraph, etc.) + $socialVars = app(WebsiteSocialVarsBuilder::class)->forSite($site)->get(); + */ + // Compartir a todas las vistas públicas + View::share([ + '_layout' => $layout, + '_seo' => $seo, + '_social' => $social, + '_contact' => $contact, + ]); + + return $next($request); + } +} diff --git a/src/Application/Http/Middleware/WebsiteTemplateMiddleware.php b/src/Application/Http/Middleware/WebsiteTemplateMiddleware.php new file mode 100644 index 0000000..09bed30 --- /dev/null +++ b/src/Application/Http/Middleware/WebsiteTemplateMiddleware.php @@ -0,0 +1,33 @@ +header('Accept'), 'text/html')) { + View::share([ + '_web' => []//app(WebsiteVarsBuilderService::class)->getWebsiteVars(), + //'_menu' => app(WebsiteMenuBuilderService::class)->getForUser(), + //'_breadcrumbs' => app(WebsiteBreadcrumbsBuilderService::class)->getBreadcrumbs(), + ]); + } + + return $next($request); + } +} diff --git a/src/Application/LocalModule.php b/src/Application/LocalModule.php new file mode 100644 index 0000000..ee9e178 --- /dev/null +++ b/src/Application/LocalModule.php @@ -0,0 +1,11 @@ +driver = config('image.driver', 'gd'); + $this->settings = $settings->self(); + } + + /** + * Procesa y guarda múltiples versiones del favicon. + * + * @param \Illuminate\Http\UploadedFile $image + * @return void + */ + public function processAndSaveFavicon(\Illuminate\Http\UploadedFile $image): void + { + Storage::makeDirectory("{$this->imageDisk}/{$this->faviconBasePath}"); + + $currentNamespace = $this->settings->get('favicon_ns'); + if ($currentNamespace) { + $this->deleteOldFiles($this->generateFaviconPaths($currentNamespace)); + } + + $imageManager = new ImageManager($this->driver); + $baseName = uniqid('favicon_', true); + + foreach ($this->getFaviconSizes() as $size => [$w, $h]) { + $resized = $imageManager->read($image->getRealPath())->cover($w, $h); + Storage::disk($this->imageDisk) + ->put("{$this->faviconBasePath}{$baseName}_{$size}.png", $resized->toPng(indexed: true)); + } + + $this->settings->set('favicon_ns', "{$this->faviconBasePath}{$baseName}"); + } + + /** + * Procesa y guarda versiones de imagen de logo. + * + * @param \Illuminate\Http\UploadedFile $image + * @param string $type + * @return void + */ + public function processAndSaveImageLogo(\Illuminate\Http\UploadedFile $image, string $type = ''): void + { + Storage::makeDirectory("{$this->imageDisk}/{$this->logoBasePath}"); + + $this->deleteOldLogoImages($type); + + $imageManager = new ImageManager($this->driver); + $original = $imageManager->read($image->getRealPath()); + + $this->saveResizedLogo($original, 22500, 'small', $type); + $this->saveResizedLogo($original, 75625, 'medium', $type); + $this->saveResizedLogo($original, 262144, '', $type); + $this->saveBase64Logo($original, 230400, $type); + } + + /** + * Redimensiona y guarda un logo. + */ + private function saveResizedLogo($image, int $maxPixels, string $suffix = '', string $type = ''): void + { + $resized = clone $image; + $this->resizeImageToMaxPixels($resized, $maxPixels); + + $fileName = uniqid("logo_{$suffix}{$type}", true) . '.png'; + $path = "{$this->logoBasePath}{$fileName}"; + + Storage::disk($this->imageDisk)->put($path, $resized->toPng(indexed: true)); + + $this->settings->set("image_logo" . ($suffix ? "_{$suffix}" : '') . ($type ? "_{$type}" : ''), $path); + } + + /** + * Guarda un logo en formato base64. + */ + private function saveBase64Logo($image, int $maxPixels, string $type = ''): void + { + $resized = clone $image; + $this->resizeImageToMaxPixels($resized, $maxPixels); + + $base64 = (string) $resized->toJpg(40)->toDataUri(); + + $this->settings->set("image_logo_base64" . ($type ? "_{$type}" : ''), $base64); + } + + /** + * Elimina archivos de imágenes antiguos. + */ + private function deleteOldFiles(array $paths): void + { + foreach ($paths as $path) { + if (Storage::disk($this->imageDisk)->exists($path)) { + Storage::disk($this->imageDisk)->delete($path); + } + } + } + + /** + * Elimina versiones anteriores de logos. + */ + private function deleteOldLogoImages(string $type = ''): void + { + $keys = [ + "image_logo" . ($type ? "_{$type}" : ''), + "image_logo_small" . ($type ? "_{$type}" : ''), + "image_logo_medium" . ($type ? "_{$type}" : ''), + ]; + + $paths = []; + + foreach ($keys as $key) { + $path = $this->settings->get($key); + if ($path) { + $paths[] = $path; + } + } + + $this->deleteOldFiles($paths); + } + + /** + * Redimensiona imagen conservando aspecto. + */ + private function resizeImageToMaxPixels($image, int $maxPixels) + { + $originalWidth = $image->width(); + $originalHeight = $image->height(); + $aspectRatio = $originalWidth / $originalHeight; + + if ($aspectRatio > 1) { + $newWidth = sqrt($maxPixels * $aspectRatio); + $newHeight = $newWidth / $aspectRatio; + + } else { + $newHeight = sqrt($maxPixels / $aspectRatio); + $newWidth = $newHeight * $aspectRatio; + } + + $image->resize( + (int) round($newWidth), + (int) round($newHeight), + function ($constraint) { + $constraint->aspectRatio(); + $constraint->upsize(); + } + ); + + return $image; + } + + + /** + * Obtiene los tamaños estándar para favicons. + */ + private function getFaviconSizes(): array + { + return [ + '16x16' => [16, 16], + '76x76' => [76, 76], + '120x120' => [120, 120], + '152x152' => [152, 152], + '180x180' => [180, 180], + '192x192' => [192, 192], + ]; + } + + /** + * Genera las rutas de favicons a eliminar. + */ + private function generateFaviconPaths(string $base): array + { + return array_map(fn($size) => "{$this->faviconBasePath}{$base}_{$size}.png", array_keys($this->getFaviconSizes())); + } +} diff --git a/Services/WebsiteSettingsService.php b/src/Application/Template/___WebsiteSettingsService.php similarity index 98% rename from Services/WebsiteSettingsService.php rename to src/Application/Template/___WebsiteSettingsService.php index bbe1c5e..4b9cc44 100644 --- a/Services/WebsiteSettingsService.php +++ b/src/Application/Template/___WebsiteSettingsService.php @@ -1,11 +1,13 @@ resetForm(); + } + + public function save() + { + if ($this->google_analytics_enabled) { + $this->validate([ + 'google_analytics_id' => 'required|string|min:12|max:30', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('google.analytics_enabled', $this->google_analytics_enabled, null, 'vuexy-website-admin'); + $SettingsService->set('google.analytics_id', $this->google_analytics_id, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('google'); + + $this->google_analytics_enabled = $settings['analytics']['enabled']; + $this->google_analytics_id = $settings['analytics']['id']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.analytics.google-analytics.google-analytics-card'); + } +} diff --git a/src/Application/UI/Livewire/Analytics/GoogleSearchConsole/GoogleSearchConsoleCard.php b/src/Application/UI/Livewire/Analytics/GoogleSearchConsole/GoogleSearchConsoleCard.php new file mode 100644 index 0000000..de9cd4f --- /dev/null +++ b/src/Application/UI/Livewire/Analytics/GoogleSearchConsole/GoogleSearchConsoleCard.php @@ -0,0 +1,65 @@ +resetForm(); + } + + public function save() + { + if ($this->google_analytics_enabled) { + $this->validate([ + 'google_analytics_id' => 'required|string|min:12|max:30', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('google.analytics_enabled', $this->google_analytics_enabled, null, 'vuexy-website-admin'); + $SettingsService->set('google.analytics_id', $this->google_analytics_id, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('google'); + + $this->google_analytics_enabled = $settings['analytics']['enabled']; + $this->google_analytics_id = $settings['analytics']['id']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.analytics.google-search-console.google-search-console-card'); + } +} diff --git a/src/Application/UI/Livewire/Analytics/GoogleTags/GoogleTagsCard.php b/src/Application/UI/Livewire/Analytics/GoogleTags/GoogleTagsCard.php new file mode 100644 index 0000000..07cd066 --- /dev/null +++ b/src/Application/UI/Livewire/Analytics/GoogleTags/GoogleTagsCard.php @@ -0,0 +1,65 @@ +resetForm(); + } + + public function save() + { + if ($this->google_analytics_enabled) { + $this->validate([ + 'google_analytics_id' => 'required|string|min:12|max:30', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('google.analytics_enabled', $this->google_analytics_enabled, null, 'vuexy-website-admin'); + $SettingsService->set('google.analytics_id', $this->google_analytics_id, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('google'); + + $this->google_analytics_enabled = $settings['analytics']['enabled']; + $this->google_analytics_id = $settings['analytics']['id']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.analytics.google-tags.google-tags-card'); + } +} diff --git a/src/Application/UI/Livewire/Analytics/PixelMeta/PixelMetaCard.php b/src/Application/UI/Livewire/Analytics/PixelMeta/PixelMetaCard.php new file mode 100644 index 0000000..f50b924 --- /dev/null +++ b/src/Application/UI/Livewire/Analytics/PixelMeta/PixelMetaCard.php @@ -0,0 +1,65 @@ +resetForm(); + } + + public function save() + { + if ($this->google_analytics_enabled) { + $this->validate([ + 'google_analytics_id' => 'required|string|min:12|max:30', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('google.analytics_enabled', $this->google_analytics_enabled, null, 'vuexy-website-admin'); + $SettingsService->set('google.analytics_id', $this->google_analytics_id, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('google'); + + $this->google_analytics_enabled = $settings['analytics']['enabled']; + $this->google_analytics_id = $settings['analytics']['id']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.analytics.pixel-meta.pixel-meta-card'); + } +} diff --git a/src/Application/UI/Livewire/Blog/Article/BlogArticleOffcanvasForm.php b/src/Application/UI/Livewire/Blog/Article/BlogArticleOffcanvasForm.php new file mode 100644 index 0000000..b97a0da --- /dev/null +++ b/src/Application/UI/Livewire/Blog/Article/BlogArticleOffcanvasForm.php @@ -0,0 +1,219 @@ + 'loadFormModel', + 'confirmDeletionWarehouse' => 'loadFormModelForDeletion', + ]; + + /** + * Definición de tipos de datos que se deben castear. + * + * @var array + */ + protected $casts = [ + 'status' => 'boolean', + ]; + + /** + * Define el modelo Eloquent asociado con el formulario. + * + * @return string + */ + protected function model(): string + { + return Warehouse::class; + } + + /** + * Define los campos del formulario. + * + * @return array + */ + protected function fields(): array + { + return (new Warehouse())->getFillable(); + } + + /** + * Valores por defecto para el formulario. + * + * @return array + */ + protected function defaults(): array + { + return [ + 'priority' => 0, + 'status' => true, + ]; + } + + /** + * Campo que se debe enfocar cuando se abra el formulario. + * + * @return string + */ + protected function focusColumnOnOpen(): string + { + return 'code'; + } + + /** + * Define reglas de validación dinámicas basadas en el modo actual. + * + * @param string $mode El modo actual del formulario ('create', 'edit', 'delete'). + * @return array + */ + protected function dynamicRules(string $mode): array + { + switch ($mode) { + case 'create': + case 'edit': + return [ + 'store_id' => ['required', 'integer', 'exists:stores,id'], + 'work_center_id' => ['nullable', 'integer', 'exists:store_work_centers,id'], + 'code' => ['required', 'string', 'max:16', Rule::unique('warehouses', 'code')->ignore($this->id)], + 'name' => ['required', 'string', 'max:96'], + 'description' => ['nullable', 'string', 'max:1024'], + 'manager_id' => ['nullable', 'integer', 'exists:users,id'], + 'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'tel2' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'priority' => ['nullable', 'numeric', 'between:0,99'], + 'status' => ['nullable', 'boolean'], + ]; + + case 'delete': + return [ + 'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación + ]; + + default: + return []; + } + } + + // ===================== VALIDACIONES ===================== + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + protected function attributes(): array + { + return [ + 'code' => 'código de almacén', + 'name' => 'nombre del almacén', + ]; + } + + /** + * Get the error messages for the defined validation rules. + * + * @return array + */ + protected function messages(): array + { + return [ + 'store_id.required' => 'El almacén debe estar asociado a un negocio.', + 'code.required' => 'El código del almacén es obligatorio.', + 'code.unique' => 'Este código ya está en uso por otro almacén.', + 'name.required' => 'El nombre del almacén es obligatorio.', + ]; + } + + /** + * Carga el formulario con datos del almacén y actualiza las opciones dinámicas. + * + * @param int $id + */ + public function loadFormModel($id): void + { + parent::loadFormModel($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Carga el formulario para eliminar un almacén, actualizando las opciones necesarias. + * + * @param int $id + */ + public function loadFormModelForDeletion($id): void + { + parent::loadFormModelForDeletion($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Define las opciones de los selectores desplegables. + * + * @return array + */ + protected function options(): array + { + $storeCatalogService = app(StoreCatalogService::class); + $contactCatalogService = app(ContactCatalogService::class); + + return [ + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), + ]; + } + + /** + * Ruta de la vista asociada con este formulario. + * + * @return string + */ + protected function viewPath(): string + { + return 'vuexy-website-admin::livewire.faq.offcanvas-form'; + } +} diff --git a/src/Application/UI/Livewire/Blog/Article/BlogArticlesTable.php b/src/Application/UI/Livewire/Blog/Article/BlogArticlesTable.php new file mode 100644 index 0000000..9c02d6c --- /dev/null +++ b/src/Application/UI/Livewire/Blog/Article/BlogArticlesTable.php @@ -0,0 +1,27 @@ + 'loadFormModel', + 'confirmDeletionWarehouse' => 'loadFormModelForDeletion', + ]; + + /** + * Definición de tipos de datos que se deben castear. + * + * @var array + */ + protected $casts = [ + 'status' => 'boolean', + ]; + + /** + * Define el modelo Eloquent asociado con el formulario. + * + * @return string + */ + protected function model(): string + { + return Warehouse::class; + } + + /** + * Define los campos del formulario. + * + * @return array + */ + protected function fields(): array + { + return (new Warehouse())->getFillable(); + } + + /** + * Valores por defecto para el formulario. + * + * @return array + */ + protected function defaults(): array + { + return [ + 'priority' => 0, + 'status' => true, + ]; + } + + /** + * Campo que se debe enfocar cuando se abra el formulario. + * + * @return string + */ + protected function focusColumnOnOpen(): string + { + return 'code'; + } + + /** + * Define reglas de validación dinámicas basadas en el modo actual. + * + * @param string $mode El modo actual del formulario ('create', 'edit', 'delete'). + * @return array + */ + protected function dynamicRules(string $mode): array + { + switch ($mode) { + case 'create': + case 'edit': + return [ + 'store_id' => ['required', 'integer', 'exists:stores,id'], + 'work_center_id' => ['nullable', 'integer', 'exists:store_work_centers,id'], + 'code' => ['required', 'string', 'max:16', Rule::unique('warehouses', 'code')->ignore($this->id)], + 'name' => ['required', 'string', 'max:96'], + 'description' => ['nullable', 'string', 'max:1024'], + 'manager_id' => ['nullable', 'integer', 'exists:users,id'], + 'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'tel2' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'priority' => ['nullable', 'numeric', 'between:0,99'], + 'status' => ['nullable', 'boolean'], + ]; + + case 'delete': + return [ + 'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación + ]; + + default: + return []; + } + } + + // ===================== VALIDACIONES ===================== + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + protected function attributes(): array + { + return [ + 'code' => 'código de almacén', + 'name' => 'nombre del almacén', + ]; + } + + /** + * Get the error messages for the defined validation rules. + * + * @return array + */ + protected function messages(): array + { + return [ + 'store_id.required' => 'El almacén debe estar asociado a un negocio.', + 'code.required' => 'El código del almacén es obligatorio.', + 'code.unique' => 'Este código ya está en uso por otro almacén.', + 'name.required' => 'El nombre del almacén es obligatorio.', + ]; + } + + /** + * Carga el formulario con datos del almacén y actualiza las opciones dinámicas. + * + * @param int $id + */ + public function loadFormModel($id): void + { + parent::loadFormModel($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Carga el formulario para eliminar un almacén, actualizando las opciones necesarias. + * + * @param int $id + */ + public function loadFormModelForDeletion($id): void + { + parent::loadFormModelForDeletion($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Define las opciones de los selectores desplegables. + * + * @return array + */ + protected function options(): array + { + $storeCatalogService = app(StoreCatalogService::class); + $contactCatalogService = app(ContactCatalogService::class); + + return [ + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), + ]; + } + + /** + * Ruta de la vista asociada con este formulario. + * + * @return string + */ + protected function viewPath(): string + { + return 'vuexy-website-admin::livewire.faq.offcanvas-form'; + } +} diff --git a/src/Application/UI/Livewire/Blog/Comment/BlogCommentOffcanvasForm.php b/src/Application/UI/Livewire/Blog/Comment/BlogCommentOffcanvasForm.php new file mode 100644 index 0000000..527d132 --- /dev/null +++ b/src/Application/UI/Livewire/Blog/Comment/BlogCommentOffcanvasForm.php @@ -0,0 +1,219 @@ + 'loadFormModel', + 'confirmDeletionWarehouse' => 'loadFormModelForDeletion', + ]; + + /** + * Definición de tipos de datos que se deben castear. + * + * @var array + */ + protected $casts = [ + 'status' => 'boolean', + ]; + + /** + * Define el modelo Eloquent asociado con el formulario. + * + * @return string + */ + protected function model(): string + { + return Warehouse::class; + } + + /** + * Define los campos del formulario. + * + * @return array + */ + protected function fields(): array + { + return (new Warehouse())->getFillable(); + } + + /** + * Valores por defecto para el formulario. + * + * @return array + */ + protected function defaults(): array + { + return [ + 'priority' => 0, + 'status' => true, + ]; + } + + /** + * Campo que se debe enfocar cuando se abra el formulario. + * + * @return string + */ + protected function focusColumnOnOpen(): string + { + return 'code'; + } + + /** + * Define reglas de validación dinámicas basadas en el modo actual. + * + * @param string $mode El modo actual del formulario ('create', 'edit', 'delete'). + * @return array + */ + protected function dynamicRules(string $mode): array + { + switch ($mode) { + case 'create': + case 'edit': + return [ + 'store_id' => ['required', 'integer', 'exists:stores,id'], + 'work_center_id' => ['nullable', 'integer', 'exists:store_work_centers,id'], + 'code' => ['required', 'string', 'max:16', Rule::unique('warehouses', 'code')->ignore($this->id)], + 'name' => ['required', 'string', 'max:96'], + 'description' => ['nullable', 'string', 'max:1024'], + 'manager_id' => ['nullable', 'integer', 'exists:users,id'], + 'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'tel2' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'priority' => ['nullable', 'numeric', 'between:0,99'], + 'status' => ['nullable', 'boolean'], + ]; + + case 'delete': + return [ + 'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación + ]; + + default: + return []; + } + } + + // ===================== VALIDACIONES ===================== + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + protected function attributes(): array + { + return [ + 'code' => 'código de almacén', + 'name' => 'nombre del almacén', + ]; + } + + /** + * Get the error messages for the defined validation rules. + * + * @return array + */ + protected function messages(): array + { + return [ + 'store_id.required' => 'El almacén debe estar asociado a un negocio.', + 'code.required' => 'El código del almacén es obligatorio.', + 'code.unique' => 'Este código ya está en uso por otro almacén.', + 'name.required' => 'El nombre del almacén es obligatorio.', + ]; + } + + /** + * Carga el formulario con datos del almacén y actualiza las opciones dinámicas. + * + * @param int $id + */ + public function loadFormModel($id): void + { + parent::loadFormModel($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Carga el formulario para eliminar un almacén, actualizando las opciones necesarias. + * + * @param int $id + */ + public function loadFormModelForDeletion($id): void + { + parent::loadFormModelForDeletion($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Define las opciones de los selectores desplegables. + * + * @return array + */ + protected function options(): array + { + $storeCatalogService = app(StoreCatalogService::class); + $contactCatalogService = app(ContactCatalogService::class); + + return [ + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), + ]; + } + + /** + * Ruta de la vista asociada con este formulario. + * + * @return string + */ + protected function viewPath(): string + { + return 'vuexy-website-admin::livewire.faq.offcanvas-form'; + } +} diff --git a/src/Application/UI/Livewire/Blog/Comment/BlogCommentsTable.php b/src/Application/UI/Livewire/Blog/Comment/BlogCommentsTable.php new file mode 100644 index 0000000..bd649c0 --- /dev/null +++ b/src/Application/UI/Livewire/Blog/Comment/BlogCommentsTable.php @@ -0,0 +1,27 @@ + 'loadFormModel', + 'confirmDeletionTag' => 'loadFormModelForDeletion', + ]; + + /** + * Definición de tipos de datos que se deben castear. + * + * @var array + */ + protected $casts = [ + 'status' => 'boolean', + ]; + + /** + * Define el modelo Eloquent asociado con el formulario. + * + * @return string + */ + protected function model(): string + { + return Tag::class; + } + + /** + * Define los campos del formulario. + * + * @return array + */ + protected function fields(): array + { + return (new Tag())->getFillable(); + } + + /** + * Valores por defecto para el formulario. + * + * @return array + */ + protected function defaults(): array + { + return [ + 'priority' => 0, + 'status' => true, + ]; + } + + /** + * Campo que se debe enfocar cuando se abra el formulario. + * + * @return string + */ + protected function focusColumnOnOpen(): string + { + return 'code'; + } + + /** + * Define reglas de validación dinámicas basadas en el modo actual. + * + * @param string $mode El modo actual del formulario ('create', 'edit', 'delete'). + * @return array + */ + protected function dynamicRules(string $mode): array + { + switch ($mode) { + case 'create': + case 'edit': + return [ + 'store_id' => ['required', 'integer', 'exists:stores,id'], + 'work_center_id' => ['nullable', 'integer', 'exists:store_work_centers,id'], + 'code' => ['required', 'string', 'max:16', Rule::unique('Tags', 'code')->ignore($this->id)], + 'name' => ['required', 'string', 'max:96'], + 'description' => ['nullable', 'string', 'max:1024'], + 'manager_id' => ['nullable', 'integer', 'exists:users,id'], + 'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'tel2' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'priority' => ['nullable', 'numeric', 'between:0,99'], + 'status' => ['nullable', 'boolean'], + ]; + + case 'delete': + return [ + 'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación + ]; + + default: + return []; + } + } + + // ===================== VALIDACIONES ===================== + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + protected function attributes(): array + { + return [ + 'code' => 'código de almacén', + 'name' => 'nombre del almacén', + ]; + } + + /** + * Get the error messages for the defined validation rules. + * + * @return array + */ + protected function messages(): array + { + return [ + 'store_id.required' => 'El almacén debe estar asociado a un negocio.', + 'code.required' => 'El código del almacén es obligatorio.', + 'code.unique' => 'Este código ya está en uso por otro almacén.', + 'name.required' => 'El nombre del almacén es obligatorio.', + ]; + } + + /** + * Carga el formulario con datos del almacén y actualiza las opciones dinámicas. + * + * @param int $id + */ + public function loadFormModel($id): void + { + parent::loadFormModel($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Carga el formulario para eliminar un almacén, actualizando las opciones necesarias. + * + * @param int $id + */ + public function loadFormModelForDeletion($id): void + { + parent::loadFormModelForDeletion($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Define las opciones de los selectores desplegables. + * + * @return array + */ + protected function options(): array + { + $storeCatalogService = app(StoreCatalogService::class); + $contactCatalogService = app(ContactCatalogService::class); + + return [ + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), + ]; + } + + /** + * Ruta de la vista asociada con este formulario. + * + * @return string + */ + protected function viewPath(): string + { + return 'vuexy-website-admin::livewire.faq.offcanvas-form'; + } +} diff --git a/src/Application/UI/Livewire/Blog/Tag/BlogTagsTable.php b/src/Application/UI/Livewire/Blog/Tag/BlogTagsTable.php new file mode 100644 index 0000000..1da23e2 --- /dev/null +++ b/src/Application/UI/Livewire/Blog/Tag/BlogTagsTable.php @@ -0,0 +1,27 @@ +resetForm(); + } + + public function save() + { + if ($this->chat_provider == 'whatsapp') { + $this->validate([ + 'chat_whatsapp_number' => 'required|string|max:20', + 'chat_whatsapp_message' => 'required|string|max:255', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('chat.provider', $this->chat_provider, null, 'vuexy-website-admin'); + $SettingsService->set('chat.whatsapp_number', $this->chat_whatsapp_number, null, 'vuexy-website-admin'); + $SettingsService->set('chat.whatsapp_message', $this->chat_whatsapp_message, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('chat'); + + $this->chat_provider = $settings['provider']; + $this->chat_whatsapp_number = $settings['whatsapp_number']; + $this->chat_whatsapp_message = $settings['whatsapp_message']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.comunication.messenger.messenger-card'); + } +} diff --git a/Livewire/VuexyWebsiteAdmin/ChatSettings.php b/src/Application/UI/Livewire/Comunication/TawkTo/TawkToCard.php similarity index 83% rename from Livewire/VuexyWebsiteAdmin/ChatSettings.php rename to src/Application/UI/Livewire/Comunication/TawkTo/TawkToCard.php index dc756ed..a152c5f 100644 --- a/Livewire/VuexyWebsiteAdmin/ChatSettings.php +++ b/src/Application/UI/Livewire/Comunication/TawkTo/TawkToCard.php @@ -1,12 +1,14 @@ resetForm(); + } + + public function save() + { + if ($this->chat_provider == 'whatsapp') { + $this->validate([ + 'chat_whatsapp_number' => 'required|string|max:20', + 'chat_whatsapp_message' => 'required|string|max:255', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('chat.provider', $this->chat_provider, null, 'vuexy-website-admin'); + $SettingsService->set('chat.whatsapp_number', $this->chat_whatsapp_number, null, 'vuexy-website-admin'); + $SettingsService->set('chat.whatsapp_message', $this->chat_whatsapp_message, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('chat'); + + $this->chat_provider = $settings['provider']; + $this->chat_whatsapp_number = $settings['whatsapp_number']; + $this->chat_whatsapp_message = $settings['whatsapp_message']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.comunication.whatsapp.whatsapp-card'); + } +} diff --git a/Livewire/VuexyWebsiteAdmin/ContactFormSettings.php b/src/Application/UI/Livewire/Contact/Form/ContactFormCard.php similarity index 81% rename from Livewire/VuexyWebsiteAdmin/ContactFormSettings.php rename to src/Application/UI/Livewire/Contact/Form/ContactFormCard.php index 4ac685b..bbd9ee0 100644 --- a/Livewire/VuexyWebsiteAdmin/ContactFormSettings.php +++ b/src/Application/UI/Livewire/Contact/Form/ContactFormCard.php @@ -1,14 +1,16 @@ $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]), - 'manager_options' => $contactCatalogService->searchCatalog('users', '', ['limit' => -1]), + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), ]; } diff --git a/src/Application/UI/Livewire/Content/Gallery/GalleryIndex.php b/src/Application/UI/Livewire/Content/Gallery/GalleryIndex.php new file mode 100644 index 0000000..9daa2e2 --- /dev/null +++ b/src/Application/UI/Livewire/Content/Gallery/GalleryIndex.php @@ -0,0 +1,15 @@ + $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]), - 'manager_options' => $contactCatalogService->searchCatalog('users', '', ['limit' => -1]), + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), ]; } diff --git a/src/Application/UI/Livewire/Seo/Canonical/CanonicalIndex.php b/src/Application/UI/Livewire/Seo/Canonical/CanonicalIndex.php new file mode 100644 index 0000000..1b2fe2b --- /dev/null +++ b/src/Application/UI/Livewire/Seo/Canonical/CanonicalIndex.php @@ -0,0 +1,40 @@ +urls = SitemapUrl::all(); + } + + public function addUrl() + { + SitemapUrl::create([ + 'url' => $this->newUrl, + 'changefreq' => $this->changefreq, + 'priority' => $this->priority, + 'lastmod' => now() + ]); + $this->reset(['newUrl', 'changefreq', 'priority']); + $this->mount(); + } + + public function deleteUrl($id) + { + SitemapUrl::find($id)->delete(); + $this->mount(); + } + + public function render() + { + return view('vuexy-website-admin::livewire.seo.canonical.index', ['urls' => $this->urls]); + }} diff --git a/src/Application/UI/Livewire/Seo/Jsonld/JsonldIndex.php b/src/Application/UI/Livewire/Seo/Jsonld/JsonldIndex.php new file mode 100644 index 0000000..9d3ac53 --- /dev/null +++ b/src/Application/UI/Livewire/Seo/Jsonld/JsonldIndex.php @@ -0,0 +1,41 @@ +urls = SitemapUrl::all(); + } + + public function addUrl() + { + SitemapUrl::create([ + 'url' => $this->newUrl, + 'changefreq' => $this->changefreq, + 'priority' => $this->priority, + 'lastmod' => now() + ]); + $this->reset(['newUrl', 'changefreq', 'priority']); + $this->mount(); + } + + public function deleteUrl($id) + { + SitemapUrl::find($id)->delete(); + $this->mount(); + } + + public function render() + { + return view('vuexy-website-admin::livewire.seo.jsonld.index', ['urls' => $this->urls]); + } +} diff --git a/src/Application/UI/Livewire/Seo/Manifest/ManifestCard.php b/src/Application/UI/Livewire/Seo/Manifest/ManifestCard.php new file mode 100644 index 0000000..d4b081e --- /dev/null +++ b/src/Application/UI/Livewire/Seo/Manifest/ManifestCard.php @@ -0,0 +1,40 @@ +urls = SitemapUrl::all(); + } + + public function addUrl() + { + SitemapUrl::create([ + 'url' => $this->newUrl, + 'changefreq' => $this->changefreq, + 'priority' => $this->priority, + 'lastmod' => now() + ]); + $this->reset(['newUrl', 'changefreq', 'priority']); + $this->mount(); + } + + public function deleteUrl($id) + { + SitemapUrl::find($id)->delete(); + $this->mount(); + } + + public function render() + { + return view('vuexy-website-admin::livewire.seo.manifest.manifest-card', ['urls' => $this->urls]); + }} diff --git a/src/Application/UI/Livewire/Seo/Robots/RobotsCard.php b/src/Application/UI/Livewire/Seo/Robots/RobotsCard.php new file mode 100644 index 0000000..fdf3a52 --- /dev/null +++ b/src/Application/UI/Livewire/Seo/Robots/RobotsCard.php @@ -0,0 +1,41 @@ +urls = SitemapUrl::all(); + } + + public function addUrl() + { + SitemapUrl::create([ + 'url' => $this->newUrl, + 'changefreq' => $this->changefreq, + 'priority' => $this->priority, + 'lastmod' => now() + ]); + $this->reset(['newUrl', 'changefreq', 'priority']); + $this->mount(); + } + + public function deleteUrl($id) + { + SitemapUrl::find($id)->delete(); + $this->mount(); + } + + public function render() + { + return view('vuexy-website-admin::livewire.seo.robots.robot-card', ['urls' => $this->urls]); + } +} diff --git a/Livewire/SitemapManager/SitemapManagerIndex.php b/src/Application/UI/Livewire/Seo/Sitemap/SitemapIndex.php similarity index 75% rename from Livewire/SitemapManager/SitemapManagerIndex.php rename to src/Application/UI/Livewire/Seo/Sitemap/SitemapIndex.php index 836d8e8..089caa3 100644 --- a/Livewire/SitemapManager/SitemapManagerIndex.php +++ b/src/Application/UI/Livewire/Seo/Sitemap/SitemapIndex.php @@ -1,11 +1,13 @@ $this->urls]); + return view('vuexy-website-admin::livewire.seo.sitemap.index', ['urls' => $this->urls]); }} diff --git a/Livewire/SitemapManager/SitemapUrlOffcanvasForm.php b/src/Application/UI/Livewire/Seo/Sitemap/SitemapUrlOffcanvasForm.php similarity index 90% rename from Livewire/SitemapManager/SitemapUrlOffcanvasForm.php rename to src/Application/UI/Livewire/Seo/Sitemap/SitemapUrlOffcanvasForm.php index 8959574..25ad134 100644 --- a/Livewire/SitemapManager/SitemapUrlOffcanvasForm.php +++ b/src/Application/UI/Livewire/Seo/Sitemap/SitemapUrlOffcanvasForm.php @@ -1,11 +1,13 @@ $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]), - 'manager_options' => $contactCatalogService->searchCatalog('users', '', ['limit' => -1]), + 'store_options' => $storeCatalogService->getCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->getCatalog('users', '', ['limit' => -1]), ]; } @@ -212,6 +214,6 @@ class SitemapUrlOffcanvasForm extends AbstractFormOffCanvasComponent */ protected function viewPath(): string { - return 'vuexy-website-admin::livewire.sitemap-manager.offcanvas-form'; + return 'vuexy-website-admin::livewire.seo.sitemap-manager.offcanvas-form'; } } diff --git a/src/Application/UI/Livewire/Seo/SocialCards/SocialCardsIndex.php b/src/Application/UI/Livewire/Seo/SocialCards/SocialCardsIndex.php new file mode 100644 index 0000000..0991724 --- /dev/null +++ b/src/Application/UI/Livewire/Seo/SocialCards/SocialCardsIndex.php @@ -0,0 +1,40 @@ +urls = SitemapUrl::all(); + } + + public function addUrl() + { + SitemapUrl::create([ + 'url' => $this->newUrl, + 'changefreq' => $this->changefreq, + 'priority' => $this->priority, + 'lastmod' => now() + ]); + $this->reset(['newUrl', 'changefreq', 'priority']); + $this->mount(); + } + + public function deleteUrl($id) + { + SitemapUrl::find($id)->delete(); + $this->mount(); + } + + public function render() + { + return view('vuexy-website-admin::livewire.seo.social-cards.index', ['urls' => $this->urls]); + }} diff --git a/Livewire/VuexyWebsiteAdmin/LogoOnDarkBgSettings.php b/src/Application/UI/Livewire/Settings/General/LogoOnDarkBgCard.php similarity index 64% rename from Livewire/VuexyWebsiteAdmin/LogoOnDarkBgSettings.php rename to src/Application/UI/Livewire/Settings/General/LogoOnDarkBgCard.php index 3ef70ea..d1639d8 100644 --- a/Livewire/VuexyWebsiteAdmin/LogoOnDarkBgSettings.php +++ b/src/Application/UI/Livewire/Settings/General/LogoOnDarkBgCard.php @@ -1,24 +1,26 @@ resetForm(); + $this->loadForm(); } public function save() @@ -34,7 +36,7 @@ class LogoOnDarkBgSettings extends Component app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); // Recargamos el formulario - $this->resetForm(); + $this->loadForm(); // Notificación de éxito $this->dispatch( @@ -45,17 +47,17 @@ class LogoOnDarkBgSettings extends Component ); } - public function resetForm() + public function loadForm() { // Obtener los valores de las configuraciones de la base de datos - $settings = app(WebsiteTemplateService::class)->getWebsiteVars(); + //$settings = app(WebsiteTemplateService::class)->getWebsiteVars(); $this->upload_image_logo_dark = null; - $this->website_image_logo_dark = $settings['image_logo']['large_dark']; + //$this->website_image_logo_dark = $settings['image_logo']['large_dark']; } public function render() { - return view('vuexy-website-admin::livewire.vuexy.logo-on-dark-bg-settings'); + return view('vuexy-website-admin::livewire.settings.general.logo-on-dark-bg-card'); } } diff --git a/Livewire/VuexyWebsiteAdmin/LogoOnLightBgSettings.php b/src/Application/UI/Livewire/Settings/General/LogoOnLightBgCard.php similarity index 64% rename from Livewire/VuexyWebsiteAdmin/LogoOnLightBgSettings.php rename to src/Application/UI/Livewire/Settings/General/LogoOnLightBgCard.php index 2b47e4b..8c3988a 100644 --- a/Livewire/VuexyWebsiteAdmin/LogoOnLightBgSettings.php +++ b/src/Application/UI/Livewire/Settings/General/LogoOnLightBgCard.php @@ -1,24 +1,26 @@ resetForm(); + $this->loadForm(); } public function save() @@ -34,7 +36,7 @@ class LogoOnLightBgSettings extends Component app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); // Recargamos el formulario - $this->resetForm(); + $this->loadForm(); // Notificación de éxito $this->dispatch( @@ -45,17 +47,17 @@ class LogoOnLightBgSettings extends Component ); } - public function resetForm() + public function loadForm() { // Obtener los valores de las configuraciones de la base de datos - $settings = app(WebsiteTemplateService::class)->getWebsiteVars(); + //$settings = app(WebsiteTemplateService::class)->getWebsiteVars(); $this->upload_image_logo = null; - $this->website_image_logo = $settings['image_logo']['large']; + //$this->website_image_logo = $settings['image_logo']['large']; } public function render() { - return view('vuexy-website-admin::livewire.vuexy.logo-on-light-bg-settings'); + return view('vuexy-website-admin::livewire.settings.general.logo-on-light-bg-card'); } } diff --git a/Livewire/VuexyWebsiteAdmin/WebsiteDescriptionSettings.php b/src/Application/UI/Livewire/Settings/General/WebsiteDescriptionCard.php similarity index 54% rename from Livewire/VuexyWebsiteAdmin/WebsiteDescriptionSettings.php rename to src/Application/UI/Livewire/Settings/General/WebsiteDescriptionCard.php index 2603bab..4794d60 100644 --- a/Livewire/VuexyWebsiteAdmin/WebsiteDescriptionSettings.php +++ b/src/Application/UI/Livewire/Settings/General/WebsiteDescriptionCard.php @@ -1,28 +1,28 @@ resetForm(); + $this->loadForm(); } public function save() { $this->validate([ - 'title' => 'required|string|max:255', - 'description' => 'nullable|string|max:255', + 'title' => 'required|string|max:255', ]); // Guardar título del sitio en configuraciones @@ -35,7 +35,7 @@ class WebsiteDescriptionSettings extends Component app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); // Recargamos el formulario - $this->resetForm(); + $this->loadForm(); // Notificación de éxito $this->dispatch( @@ -46,17 +46,17 @@ class WebsiteDescriptionSettings extends Component ); } - public function resetForm() + public function loadForm() { // Obtener los valores de las configuraciones de la base de datos - $settings = app(WebsiteTemplateService::class)->getWebsiteVars(); + //$settings = app(WebsiteTemplateService::class)->getWebsiteVars(); - $this->title = $settings['title']; - $this->description = $settings['description']; + //$this->title = $settings['title']; + //$this->description = $settings['description']; } public function render() { - return view('vuexy-website-admin::livewire.vuexy.website-description-settings'); + return view('vuexy-website-admin::livewire.settings.general.website-description-card'); } } diff --git a/Livewire/VuexyWebsiteAdmin/WebsiteFaviconSettings.php b/src/Application/UI/Livewire/Settings/General/WebsiteFaviconCard.php similarity index 78% rename from Livewire/VuexyWebsiteAdmin/WebsiteFaviconSettings.php rename to src/Application/UI/Livewire/Settings/General/WebsiteFaviconCard.php index 2666e24..2297dff 100644 --- a/Livewire/VuexyWebsiteAdmin/WebsiteFaviconSettings.php +++ b/src/Application/UI/Livewire/Settings/General/WebsiteFaviconCard.php @@ -1,17 +1,19 @@ resetForm(); + $this->loadForm(); } public function save() @@ -40,7 +42,7 @@ class WebsiteFaviconSettings extends Component app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); // Recargamos el formulario - $this->resetForm(); + $this->loadForm(); // Notificación de éxito $this->dispatch( @@ -51,9 +53,10 @@ class WebsiteFaviconSettings extends Component ); } - public function resetForm() + public function loadForm() { // Obtener los valores de las configuraciones de la base de datos + /* $settings = app(WebsiteTemplateService::class)->getWebsiteVars(); $this->upload_image_favicon = null; @@ -63,10 +66,11 @@ class WebsiteFaviconSettings extends Component $this->website_favicon_152x152 = $settings['favicon']['152x152']; $this->website_favicon_180x180 = $settings['favicon']['180x180']; $this->website_favicon_192x192 = $settings['favicon']['192x192']; + */ } public function render() { - return view('vuexy-website-admin::livewire.vuexy.website-favicon-settings'); + return view('vuexy-website-admin::livewire.settings.general.website-favicon-card'); } } diff --git a/src/Application/UI/Livewire/Settings/Indexing/IndexingCard.php b/src/Application/UI/Livewire/Settings/Indexing/IndexingCard.php new file mode 100644 index 0000000..0a3b633 --- /dev/null +++ b/src/Application/UI/Livewire/Settings/Indexing/IndexingCard.php @@ -0,0 +1,101 @@ +validate([ + 'social_whatsapp' => 'string|max:20', + 'social_whatsapp_message' => 'string|max:255', + 'social_facebook' => 'url', + 'social_instagram' => 'url', + 'social_linkedin' => 'url', + 'social_tiktok' => 'url', + 'social_x_twitter' => 'url', + 'social_google' => 'url', + 'social_pinterest' => 'url', + 'social_youtube' => 'url', + 'social_vimeo' => 'url', + ]); + + // Guardar título del sitio en configuraciones + $SettingsService = app(SystemSettingsService::class); + + $SettingsService->set('social.whatsapp', $this->social_whatsapp, null, 'vuexy-website-admin'); + $SettingsService->set('social.whatsapp_message', $this->social_whatsapp_message, null, 'vuexy-website-admin'); + $SettingsService->set('social.facebook', $this->social_facebook, null, 'vuexy-website-admin'); + $SettingsService->set('social.instagram', $this->social_instagram, null, 'vuexy-website-admin'); + $SettingsService->set('social.linkedin', $this->social_linkedin, null, 'vuexy-website-admin'); + $SettingsService->set('social.tiktok', $this->social_tiktok, null, 'vuexy-website-admin'); + $SettingsService->set('social.x_twitter', $this->social_x_twitter, null, 'vuexy-website-admin'); + $SettingsService->set('social.google', $this->social_google, null, 'vuexy-website-admin'); + $SettingsService->set('social.pinterest', $this->social_pinterest, null, 'vuexy-website-admin'); + $SettingsService->set('social.youtube', $this->social_youtube, null, 'vuexy-website-admin'); + $SettingsService->set('social.vimeo', $this->social_vimeo, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getSocialVars(); + + $this->social_whatsapp = $settings['whatsapp']; + $this->social_whatsapp_message = $settings['whatsapp_message']; + $this->social_facebook = $settings['facebook']; + $this->social_instagram = $settings['instagram']; + $this->social_linkedin = $settings['linkedin']; + $this->social_tiktok = $settings['tiktok']; + $this->social_x_twitter = $settings['x_twitter']; + $this->social_google = $settings['google']; + $this->social_pinterest = $settings['pinterest']; + $this->social_youtube = $settings['youtube']; + $this->social_vimeo = $settings['vimeo']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.settings.indexing.indexing-card'); + } +} diff --git a/Livewire/VuexyWebsiteAdmin/SocialMediaSettings.php b/src/Application/UI/Livewire/Settings/Social/SocialCard.php similarity index 91% rename from Livewire/VuexyWebsiteAdmin/SocialMediaSettings.php rename to src/Application/UI/Livewire/Settings/Social/SocialCard.php index 12cbd29..9c16106 100644 --- a/Livewire/VuexyWebsiteAdmin/SocialMediaSettings.php +++ b/src/Application/UI/Livewire/Settings/Social/SocialCard.php @@ -1,12 +1,14 @@ resetForm(); + } + + public function save() + { + if ($this->chat_provider == 'whatsapp') { + $this->validate([ + 'chat_whatsapp_number' => 'required|string|max:20', + 'chat_whatsapp_message' => 'required|string|max:255', + ]); + } + + // Guardar título del sitio en configuraciones + $SettingsService = app(SettingsService::class); + + $SettingsService->set('chat.provider', $this->chat_provider, null, 'vuexy-website-admin'); + $SettingsService->set('chat.whatsapp_number', $this->chat_whatsapp_number, null, 'vuexy-website-admin'); + $SettingsService->set('chat.whatsapp_message', $this->chat_whatsapp_message, null, 'vuexy-website-admin'); + + // Limpiar cache de plantilla + app(WebsiteTemplateService::class)->clearWebsiteVarsCache(); + + // Recargamos el formulario + $this->resetForm(); + + // Notificación de éxito + $this->dispatch( + 'notification', + target: $this->targetNotify, + type: 'success', + message: 'Se han guardado los cambios en las configuraciones.' + ); + } + + public function resetForm() + { + // Obtener los valores de las configuraciones de la base de datos + $settings = app(WebsiteTemplateService::class)->getWebsiteVars('chat'); + + $this->chat_provider = $settings['provider']; + $this->chat_whatsapp_number = $settings['whatsapp_number']; + $this->chat_whatsapp_message = $settings['whatsapp_message']; + } + + public function render() + { + return view('vuexy-website-admin::livewire.translate.google.google-translate-card'); + } +} diff --git a/src/Application/UIX/ConfigBuilders/Blog/ArticlesTableConfigBuilder.php b/src/Application/UIX/ConfigBuilders/Blog/ArticlesTableConfigBuilder.php new file mode 100644 index 0000000..f16c0a5 --- /dev/null +++ b/src/Application/UIX/ConfigBuilders/Blog/ArticlesTableConfigBuilder.php @@ -0,0 +1,175 @@ + 'Acciones', + 'category_name' => 'Categoría', + 'question' => 'Pregunta', + 'answer' => 'Respuesta', + 'order' => 'Orden', + 'is_active' => 'Activo', + 'created_at' => 'Creado el', + 'updated_at' => 'Actualizado el', + 'deleted_at' => 'Eliminado el', + ]; + } + + /** + * Devuelve los JOINs requeridos por la consulta. + */ + public static function getIndexJoins(): array + { + return [ + ['faq_categories', 'faqs.category_id', '=', 'faq_categories.id'] + ]; + } + + /** + * Devuelve los filtros aplicables en la tabla (como búsqueda global). + */ + public static function getIndexFilters(): array + { + return [ + 'search' => [ + 'faqs.question', + 'faqs.answer', + ] + ]; + } + + /** + * Devuelve la configuración de formatos y visibilidad para cada columna. + */ + public static function getIndexFormatters(): array + { + return [ + /* + 'action' => [ + 'formatter' => 'VehicleAssignmentsAactionFormatter', + ], + */ + /* + 'carrier_id' => [ + 'formatter' => 'profilePhotoFormatter', + ], + */ + 'vehicle_type_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'brand_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'current_insurer_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'plate_number' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'model' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'year_manufacture' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'vin' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'fuel_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'axles' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'capacity_kg' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'start_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'end_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'max_km_allowed' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'assignment_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'status' => [ + 'formatter' => 'dynamicBadgeFormatter', + ], + 'created_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'updated_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'deleted_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + ]; + } + + /** + * Devuelve las rutas del CRUD para cada fila. + */ + public static function getIndexRoutes(): array + { + return [ + 'admin.user.show' => route('admin.core.users.users.show', ['user' => ':id']), + ]; + } + + /** + * Devuelve configuraciones adicionales del Bootstrap Table. + */ + public static function getIndexTableConfig(): array + { + return [ + 'search' => true, + 'pagination' => true, + 'showExport' => true, + ]; + } +} diff --git a/src/Application/UIX/ConfigBuilders/Blog/CategoriesTableConfigBuilder.php b/src/Application/UIX/ConfigBuilders/Blog/CategoriesTableConfigBuilder.php new file mode 100644 index 0000000..10bfe56 --- /dev/null +++ b/src/Application/UIX/ConfigBuilders/Blog/CategoriesTableConfigBuilder.php @@ -0,0 +1,175 @@ + 'Acciones', + 'category_name' => 'Categoría', + 'question' => 'Pregunta', + 'answer' => 'Respuesta', + 'order' => 'Orden', + 'is_active' => 'Activo', + 'created_at' => 'Creado el', + 'updated_at' => 'Actualizado el', + 'deleted_at' => 'Eliminado el', + ]; + } + + /** + * Devuelve los JOINs requeridos por la consulta. + */ + public static function getIndexJoins(): array + { + return [ + ['faq_categories', 'faqs.category_id', '=', 'faq_categories.id'] + ]; + } + + /** + * Devuelve los filtros aplicables en la tabla (como búsqueda global). + */ + public static function getIndexFilters(): array + { + return [ + 'search' => [ + 'faqs.question', + 'faqs.answer', + ] + ]; + } + + /** + * Devuelve la configuración de formatos y visibilidad para cada columna. + */ + public static function getIndexFormatters(): array + { + return [ + /* + 'action' => [ + 'formatter' => 'VehicleAssignmentsAactionFormatter', + ], + */ + /* + 'carrier_id' => [ + 'formatter' => 'profilePhotoFormatter', + ], + */ + 'vehicle_type_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'brand_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'current_insurer_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'plate_number' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'model' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'year_manufacture' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'vin' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'fuel_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'axles' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'capacity_kg' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'start_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'end_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'max_km_allowed' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'assignment_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'status' => [ + 'formatter' => 'dynamicBadgeFormatter', + ], + 'created_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'updated_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'deleted_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + ]; + } + + /** + * Devuelve las rutas del CRUD para cada fila. + */ + public static function getIndexRoutes(): array + { + return [ + 'admin.user.show' => route('admin.core.users.users.show', ['user' => ':id']), + ]; + } + + /** + * Devuelve configuraciones adicionales del Bootstrap Table. + */ + public static function getIndexTableConfig(): array + { + return [ + 'search' => true, + 'pagination' => true, + 'showExport' => true, + ]; + } +} diff --git a/src/Application/UIX/ConfigBuilders/Blog/CommentsTableConfigBuilder.php b/src/Application/UIX/ConfigBuilders/Blog/CommentsTableConfigBuilder.php new file mode 100644 index 0000000..012ea39 --- /dev/null +++ b/src/Application/UIX/ConfigBuilders/Blog/CommentsTableConfigBuilder.php @@ -0,0 +1,175 @@ + 'Acciones', + 'category_name' => 'Categoría', + 'question' => 'Pregunta', + 'answer' => 'Respuesta', + 'order' => 'Orden', + 'is_active' => 'Activo', + 'created_at' => 'Creado el', + 'updated_at' => 'Actualizado el', + 'deleted_at' => 'Eliminado el', + ]; + } + + /** + * Devuelve los JOINs requeridos por la consulta. + */ + public static function getIndexJoins(): array + { + return [ + ['faq_categories', 'faqs.category_id', '=', 'faq_categories.id'] + ]; + } + + /** + * Devuelve los filtros aplicables en la tabla (como búsqueda global). + */ + public static function getIndexFilters(): array + { + return [ + 'search' => [ + 'faqs.question', + 'faqs.answer', + ] + ]; + } + + /** + * Devuelve la configuración de formatos y visibilidad para cada columna. + */ + public static function getIndexFormatters(): array + { + return [ + /* + 'action' => [ + 'formatter' => 'VehicleAssignmentsAactionFormatter', + ], + */ + /* + 'carrier_id' => [ + 'formatter' => 'profilePhotoFormatter', + ], + */ + 'vehicle_type_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'brand_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'current_insurer_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'plate_number' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'model' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'year_manufacture' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'vin' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'fuel_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'axles' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'capacity_kg' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'start_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'end_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'max_km_allowed' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'assignment_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'status' => [ + 'formatter' => 'dynamicBadgeFormatter', + ], + 'created_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'updated_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'deleted_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + ]; + } + + /** + * Devuelve las rutas del CRUD para cada fila. + */ + public static function getIndexRoutes(): array + { + return [ + 'admin.user.show' => route('admin.core.users.users.show', ['user' => ':id']), + ]; + } + + /** + * Devuelve configuraciones adicionales del Bootstrap Table. + */ + public static function getIndexTableConfig(): array + { + return [ + 'search' => true, + 'pagination' => true, + 'showExport' => true, + ]; + } +} diff --git a/src/Application/UIX/ConfigBuilders/Blog/TagsTableConfigBuilder.php b/src/Application/UIX/ConfigBuilders/Blog/TagsTableConfigBuilder.php new file mode 100644 index 0000000..e820f8b --- /dev/null +++ b/src/Application/UIX/ConfigBuilders/Blog/TagsTableConfigBuilder.php @@ -0,0 +1,175 @@ + 'Acciones', + 'category_name' => 'Categoría', + 'question' => 'Pregunta', + 'answer' => 'Respuesta', + 'order' => 'Orden', + 'is_active' => 'Activo', + 'created_at' => 'Creado el', + 'updated_at' => 'Actualizado el', + 'deleted_at' => 'Eliminado el', + ]; + } + + /** + * Devuelve los JOINs requeridos por la consulta. + */ + public static function getIndexJoins(): array + { + return [ + ['faq_categories', 'faqs.category_id', '=', 'faq_categories.id'] + ]; + } + + /** + * Devuelve los filtros aplicables en la tabla (como búsqueda global). + */ + public static function getIndexFilters(): array + { + return [ + 'search' => [ + 'faqs.question', + 'faqs.answer', + ] + ]; + } + + /** + * Devuelve la configuración de formatos y visibilidad para cada columna. + */ + public static function getIndexFormatters(): array + { + return [ + /* + 'action' => [ + 'formatter' => 'VehicleAssignmentsAactionFormatter', + ], + */ + /* + 'carrier_id' => [ + 'formatter' => 'profilePhotoFormatter', + ], + */ + 'vehicle_type_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'brand_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'current_insurer_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'plate_number' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'model' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'year_manufacture' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'vin' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'fuel_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'axles' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'capacity_kg' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'start_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'end_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'max_km_allowed' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'assignment_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'status' => [ + 'formatter' => 'dynamicBadgeFormatter', + ], + 'created_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'updated_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'deleted_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + ]; + } + + /** + * Devuelve las rutas del CRUD para cada fila. + */ + public static function getIndexRoutes(): array + { + return [ + 'admin.user.show' => route('admin.core.users.users.show', ['user' => ':id']), + ]; + } + + /** + * Devuelve configuraciones adicionales del Bootstrap Table. + */ + public static function getIndexTableConfig(): array + { + return [ + 'search' => true, + 'pagination' => true, + 'showExport' => true, + ]; + } +} diff --git a/src/Application/UIX/ConfigBuilders/Faq/FaqTableConfigBuilder.php b/src/Application/UIX/ConfigBuilders/Faq/FaqTableConfigBuilder.php new file mode 100644 index 0000000..cb04f13 --- /dev/null +++ b/src/Application/UIX/ConfigBuilders/Faq/FaqTableConfigBuilder.php @@ -0,0 +1,176 @@ + 'Acciones', + 'category_name' => 'Categoría', + 'question' => 'Pregunta', + 'answer' => 'Respuesta', + 'order' => 'Orden', + 'is_active' => 'Activo', + 'created_at' => 'Creado el', + 'updated_at' => 'Actualizado el', + 'deleted_at' => 'Eliminado el', + ]; + } + + /** + * Devuelve los JOINs requeridos por la consulta. + */ + public static function getIndexJoins(): array + { + return [ + ['faq_categories', 'faqs.category_id', '=', 'faq_categories.id'] + ]; + } + + /** + * Devuelve los filtros aplicables en la tabla (como búsqueda global). + */ + public static function getIndexFilters(): array + { + return [ + 'search' => [ + 'faqs.question', + 'faqs.answer', + ] + ]; + } + + /** + * Devuelve la configuración de formatos y visibilidad para cada columna. + */ + public static function getIndexFormatters(): array + { + return [ + /* + 'action' => [ + 'formatter' => 'VehicleAssignmentsAactionFormatter', + ], + */ + /* + 'carrier_id' => [ + 'formatter' => 'profilePhotoFormatter', + ], + */ + 'vehicle_type_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'brand_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'current_insurer_id' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'plate_number' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'model' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'year_manufacture' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'vin' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'fuel_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'axles' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'capacity_kg' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'start_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'end_date' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'max_km_allowed' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'assignment_type' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'status' => [ + 'formatter' => 'dynamicBadgeFormatter', + ], + 'created_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'updated_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + 'deleted_at' => [ + 'formatter' => 'textNowrapFormatter', + ], + ]; + } + + /** + * Devuelve las rutas del CRUD para cada fila. + */ + public static function getIndexRoutes(): array + { + return [ + 'admin.user.show' => route('admin.core.users.users.show', ['user' => ':id']), + ]; + } + + /** + * Devuelve configuraciones adicionales del Bootstrap Table. + */ + public static function getIndexTableConfig(): array + { + return [ + 'search' => true, + 'pagination' => true, + 'showExport' => true, + ]; + } +} diff --git a/Console/Commands/SitemapGenerate.php b/src/Console/Commands/SitemapGenerate.php similarity index 97% rename from Console/Commands/SitemapGenerate.php rename to src/Console/Commands/SitemapGenerate.php index 5a2fd67..4e71102 100644 --- a/Console/Commands/SitemapGenerate.php +++ b/src/Console/Commands/SitemapGenerate.php @@ -1,5 +1,7 @@ info('✅ Sitemap generado en storage/app/public/sitemap.xml'); } -} \ No newline at end of file +} diff --git a/src/Console/Commands/WebsiteCacheHelperCommand.php b/src/Console/Commands/WebsiteCacheHelperCommand.php new file mode 100644 index 0000000..064fcf6 --- /dev/null +++ b/src/Console/Commands/WebsiteCacheHelperCommand.php @@ -0,0 +1,65 @@ +option('slug'); + $ttl = (int) $this->option('ttl'); + + if ($this->option('summary')) { + $this->info('🔍 Claves de cacheo HTML renderizado (Redis/Tags):'); + $this->line('- (Nota: Laravel no permite listar claves directamente desde tags)'); + $this->line('💡 Usa observabilidad desde Redis CLI para inspección manual o eventos de log.'); + return self::SUCCESS; + } + + if ($this->option('clear')) { + if ($slug) { + WebsiteRenderCacheService::invalidate('website', $slug); + $this->info("🧹 Cache HTML limpiado para la página: {$slug}"); + } else { + Cache::tags(['rendered_html', 'website'])->flush(); + $this->info('🧼 Cache HTML global de website limpiado.'); + } + return self::SUCCESS; + } + + if ($this->option('simulate')) { + if (! $slug) { + $this->error('❌ Debes proporcionar un slug con --slug para simular cacheo.'); + return self::FAILURE; + } + + $content = WebsiteContent::published()->bySlug($slug)->first(); + if (! $content) { + $this->error("❌ Contenido no encontrado para slug: {$slug}"); + return self::FAILURE; + } + + $html = WebsiteRenderCacheService::getOrRender('website', $content->slug, fn() => $content->toHtml(), $ttl); + $this->info("✅ HTML cacheado para '{$slug}' con TTL de {$ttl} segundos"); + return self::SUCCESS; + } + + $this->warn('⚠️ No se especificó ninguna acción. Usa --help para ver las opciones disponibles.'); + return self::SUCCESS; + } +} diff --git a/src/Console/Commands/WebsiteContentHelperCommand.php b/src/Console/Commands/WebsiteContentHelperCommand.php new file mode 100644 index 0000000..eb0cf85 --- /dev/null +++ b/src/Console/Commands/WebsiteContentHelperCommand.php @@ -0,0 +1,114 @@ +option('slug'); + $id = $this->option('id'); + $ttl = (int) ($this->option('ttl') ?? 30); + + if ($this->option('clear-all-cache')) { + Cache::tags(['rendered_html'])->flush(); + $this->info('🧹 Caché HTML global limpiada.'); + return self::SUCCESS; + } + + if ($this->option('routes')) { + $this->info('🌐 Rutas públicas de contenido publicado:'); + WebsiteContent::published()->orderBy('id')->get()->each(function ($content, $i) { + $url = url($content->slug); + $this->line(sprintf("[%d] %s → %s", $i + 1, $url, $content->slug)); + }); + return self::SUCCESS; + } + + if ($slug || $id) { + $content = $slug + ? WebsiteContent::where('slug', $slug)->first() + : WebsiteContent::find($id); + + if (! $content) { + $this->error('❌ Contenido no encontrado.'); + return self::FAILURE; + } + + if ($this->option('clear-cache')) { + Cache::tags(["rendered_html", "website", "website_{$content->slug}"])->flush(); + $this->info("🧹 Caché HTML de '{$content->slug}' limpiada."); + } + + if ($this->option('preview')) { + $url = $content->previewUrl(auth()->id(), $ttl); + $this->line("🔍 Vista previa: {$url}"); + } + + if ($this->option('dump')) { + $this->info("📦 Contenido completo:"); + dump($content->toArray()); + } + + if ($this->option('versions')) { + $this->info("📜 Versiones:"); + foreach ($content->versions as $v) { + $this->line(" - {$v->version_label} [ID: {$v->id}] ({$v->created_at})"); + } + } + + if ($this->option('meta')) { + $this->info("📄 Metadatos SEO efectivos:"); + dump($content->getEffectiveSeoMetadata()); + } + + if ($this->option('canonical')) { + $this->line("🔗 Canonical URL: {$content->getCanonicalUrl()}"); + } + + if ($this->option('html')) { + $this->info("🖼 HTML renderizado:"); + $this->line($content->toHtml()); + } + } + + if ($this->option('summary')) { + $this->info("📚 Resumen de contenidos:"); + $all = WebsiteContent::select('id', 'slug', 'title', 'template', 'type', 'is_draft') + ->orderBy('id', 'asc')->get(); + + foreach ($all as $c) { + $flag = $c->is_draft ? '📝' : '✅'; + $this->line("[{$c->id}] {$flag} {$c->slug} — {$c->title} ({$c->template})"); + } + } + + if (! $slug && ! $id && ! $this->option('summary') && ! $this->option('clear-all-cache') && ! $this->option('routes')) { + $this->warn("⚠️ No se especificó ninguna acción. Usa --help para ver las opciones disponibles."); + } + + return self::SUCCESS; + } +} diff --git a/src/Console/Commands/WebsiteMenuHelperCommand.php b/src/Console/Commands/WebsiteMenuHelperCommand.php new file mode 100644 index 0000000..c7f40ee --- /dev/null +++ b/src/Console/Commands/WebsiteMenuHelperCommand.php @@ -0,0 +1,134 @@ +option('slug') ?? $this->option('tree'); + $summary = $this->option('summary'); + $as = $this->option('as'); + $asUserId = null; + + if ($this->option('list')) { + $this->listMenus(); + return self::SUCCESS; + } + + if ($this->option('clear-cache')) { + if ($slug) { + WebsiteMenuRenderer::clearCache($slug); + $this->info("🔁 Caché limpiada para el menú '{$slug}'"); + } else { + WebsiteMenuRenderer::clearAllCache(); + $this->info("🔁 Caché global de menú limpiada"); + } + return self::SUCCESS; + } + + if (! $slug) { + $this->error('⚠️ Debes especificar el slug del menú con --slug o --tree.'); + return self::FAILURE; + } + + $user = null; + if ($as === 'visitor') { + $user = null; + } elseif (str_starts_with($as, 'user:')) { + $asUserId = (int) str_replace('user:', '', $as); + $user = User::find($asUserId); + } + + $tree = WebsiteMenuRenderer::tree($slug, $user); + + if ($this->option('id-node')) { + $nodeId = (int) $this->option('id-node'); + $node = $this->findNodeById($tree, $nodeId); + return $this->renderOutput($node); + } + + if ($this->option('export')) { + $file = $this->option('export'); + File::put($file, json_encode($tree, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + $this->info("📦 Menú exportado a: {$file}"); + return self::SUCCESS; + } + + if ($summary) { + $this->renderSummary($tree); + return self::SUCCESS; + } + + return $this->renderOutput($tree); + } + + protected function listMenus(): void + { + $this->info("🧭 Menús disponibles:"); + foreach (WebsiteMenu::all() as $menu) { + $this->line("- [{$menu->id}] {$menu->slug} — {$menu->title}"); + } + } + + protected function renderOutput(array $tree): int + { + if ($this->option('json')) { + $this->line(json_encode($tree, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + } elseif ($this->option('dump')) { + dump($tree); + } else { + $this->warn('⚠️ Usa --json, --dump o --summary para mostrar la salida.'); + } + return self::SUCCESS; + } + + protected function renderSummary(array $items, string $prefix = ''): void + { + foreach ($items as $item) { + $id = $item['id'] ?? '??'; + $title = $item['title'] ?? '[sin título]'; + $this->line("[#{$id}] {$prefix}{$title}"); + + if (!empty($item['children'])) { + $this->renderSummary($item['children'], $prefix . ' └ '); + } + } + } + + protected function findNodeById(array $items, int $id): ?array + { + foreach ($items as $item) { + if (($item['id'] ?? null) === $id) { + return $item; + } + if (!empty($item['children'])) { + $found = $this->findNodeById($item['children'], $id); + if ($found) return $found; + } + } + return null; + } +} diff --git a/src/Console/Commands/WebsiteSeoHelperCommand.php b/src/Console/Commands/WebsiteSeoHelperCommand.php new file mode 100644 index 0000000..3f3d893 --- /dev/null +++ b/src/Console/Commands/WebsiteSeoHelperCommand.php @@ -0,0 +1,73 @@ +option('id'); + $slug = $this->option('slug'); + $activeOnly = $this->option('active'); + + if ($this->option('summary')) { + $this->info('📄 Listado de perfiles SEO:'); + $query = WebsiteSeoProfile::select('id', 'slug', 'title', 'type'); + if ($activeOnly) { + $query->active(); + } + $all = $query->orderBy('id')->get(); + + foreach ($all as $seo) { + $this->line("[{$seo->id}] {$seo->slug} — {$seo->title} ({$seo->type->value})"); + } + return self::SUCCESS; + } + + if ($id || $slug) { + $seo = $id + ? WebsiteSeoProfile::find($id) + : WebsiteSeoProfile::where('slug', $slug)->first(); + + if (! $seo) { + $this->error('❌ Perfil SEO no encontrado.'); + return self::FAILURE; + } + + if ($this->option('dump')) { + $this->info("🧾 Información completa del perfil SEO:"); + dump($seo->toArray()); + } + + if ($this->option('meta')) { + $this->info("📑 Meta Tags generados:"); + dump($seo->getMetaTags()); + } + + if ($this->option('jsonld')) { + $this->info("🧬 JSON-LD:"); + dump($seo->toJsonLd()); + } + + return self::SUCCESS; + } + + $this->warn('⚠️ No se especificó ninguna acción. Usa --help para ver las opciones disponibles.'); + return self::SUCCESS; + } +} diff --git a/src/Database/Seeders/WebsiteContentSeeder.php b/src/Database/Seeders/WebsiteContentSeeder.php new file mode 100644 index 0000000..bcce5bc --- /dev/null +++ b/src/Database/Seeders/WebsiteContentSeeder.php @@ -0,0 +1,40 @@ + $this->resolveSeederUserId(), + 'updated_by' => $this->resolveSeederUserId(), + 'is_draft' => $row['is_draft'] ?? false, + 'is_sensitive' => $row['is_sensitive'] ?? false, + 'seo_profile_id' => $this->findSeoProfileId($row['seo_profile_id']), + ]); + } + + protected function findSeoProfileId($slug): ?int + { + return WebsiteSeoProfile::where('slug', $slug)->first()?->id; + } + + protected function resolveSeederUserId(): ?int + { + return config('seeder.default_user_id') ?? 1; + } +} diff --git a/src/Database/Seeders/WebsiteMenuItemSeeder.php b/src/Database/Seeders/WebsiteMenuItemSeeder.php new file mode 100644 index 0000000..e014629 --- /dev/null +++ b/src/Database/Seeders/WebsiteMenuItemSeeder.php @@ -0,0 +1,41 @@ +firstOrFail(); + + $row['menu_id'] = $menu->id; + unset($row['menu_slug']); + + // Asegurar que title sea array + if (isset($row['title']) && is_string($row['title'])) { + $decoded = json_decode($row['title'], true); + + if (json_last_error() === JSON_ERROR_NONE) { + $row['title'] = $decoded; + } + } + + return $row; + } + +} diff --git a/src/Database/Seeders/WebsiteMenuSeeder.php b/src/Database/Seeders/WebsiteMenuSeeder.php new file mode 100644 index 0000000..ab927df --- /dev/null +++ b/src/Database/Seeders/WebsiteMenuSeeder.php @@ -0,0 +1,21 @@ + $this->resolveSeederUserId(), + ]); + } + + /** + * Opcional: usuario dummy o admin por defecto + */ + protected function resolveSeederUserId(): int|null + { + return config('seeder.default_user_id', null); // Usa config si está definido + } +} diff --git a/src/Database/Seeders/WebsiteSiteSeeder.php b/src/Database/Seeders/WebsiteSiteSeeder.php new file mode 100644 index 0000000..44c1621 --- /dev/null +++ b/src/Database/Seeders/WebsiteSiteSeeder.php @@ -0,0 +1,21 @@ + 'array', + 'is_published' => 'boolean', + 'published_at' => 'datetime', + ]; + + protected $auditInclude = [ + 'category_id', + 'title', + 'slug', + 'excerpt', + 'content', + 'metadata', + 'is_published', + 'published_at', + 'updated_by', + ]; + + public function category() : BelongsTo + { + return $this->belongsTo(BlogCategory::class); + } + + public function tags() : BelongsToMany + { + return $this->belongsToMany(BlogTag::class, 'blog_article_tag'); + } + + public function comments() : HasMany + { + return $this->hasMany(BlogComment::class); + } + + public function creator() : BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function updater() : BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->title; + } +} diff --git a/src/Models/BlogCategory.php b/src/Models/BlogCategory.php new file mode 100644 index 0000000..9edfa03 --- /dev/null +++ b/src/Models/BlogCategory.php @@ -0,0 +1,64 @@ + 'boolean', + ]; + + protected $auditInclude = [ + 'name', + 'slug', + 'parent_id', + 'description', + 'is_active', + ]; + + public function parent() : BelongsTo + { + return $this->belongsTo(self::class, 'parent_id'); + } + + public function children() : HasMany + { + return $this->hasMany(self::class, 'parent_id'); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->name; + } +} diff --git a/src/Models/BlogComment.php b/src/Models/BlogComment.php new file mode 100644 index 0000000..ec51fa2 --- /dev/null +++ b/src/Models/BlogComment.php @@ -0,0 +1,55 @@ + 'boolean', + ]; + + protected $auditInclude = [ + 'comment', + 'is_approved', + ]; + + public function article() : BelongsTo + { + return $this->belongsTo(BlogArticle::class); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->comment; + } +} diff --git a/src/Models/BlogTag.php b/src/Models/BlogTag.php new file mode 100644 index 0000000..76bc360 --- /dev/null +++ b/src/Models/BlogTag.php @@ -0,0 +1,48 @@ + 'boolean', + ]; + + protected $auditInclude = [ + 'name', + 'slug', + 'is_active', + ]; + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->name; + } +} diff --git a/src/Models/Faq.php b/src/Models/Faq.php new file mode 100644 index 0000000..bae7c44 --- /dev/null +++ b/src/Models/Faq.php @@ -0,0 +1,61 @@ + 'integer', + 'is_active' => 'boolean', + ]; + + protected $auditInclude = [ + 'category_id', + 'question', + 'answer', + 'order', + 'is_active', + ]; + + // ===================== RELACIONES ===================== + + public function category(): BelongsTo + { + return $this->belongsTo(FaqCategory::class, 'category_id'); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return (string) $this->question; + } +} diff --git a/Models/FaqCategory.php b/src/Models/FaqCategory.php similarity index 55% rename from Models/FaqCategory.php rename to src/Models/FaqCategory.php index c1feb8f..d849398 100644 --- a/Models/FaqCategory.php +++ b/src/Models/FaqCategory.php @@ -1,14 +1,19 @@ 'boolean', ]; + protected $auditInclude = [ + 'name', + 'icon', + 'order', + 'is_active', + ]; + /** * FAQs asociadas a esta categoría. */ diff --git a/src/Models/SitemapIndexFile.php b/src/Models/SitemapIndexFile.php new file mode 100644 index 0000000..cb8c4d6 --- /dev/null +++ b/src/Models/SitemapIndexFile.php @@ -0,0 +1,42 @@ + 'datetime', + 'is_current' => 'boolean', + ]; + + protected $auditInclude = [ + 'file_name', + 'url', + 'generated_at', + 'url_count', + 'is_current', + ]; + + public function profile(): BelongsTo + { + return $this->belongsTo(SitemapProfile::class, 'sitemap_profile_id'); + } +} diff --git a/src/Models/SitemapProfile.php b/src/Models/SitemapProfile.php new file mode 100644 index 0000000..2d78c5f --- /dev/null +++ b/src/Models/SitemapProfile.php @@ -0,0 +1,63 @@ + 'boolean', + ]; + + protected $auditInclude = [ + 'site_id', + 'name', + 'slug', + 'entity_type', + 'generator_class', + 'is_active', + ]; + + public function site(): BelongsTo + { + return $this->belongsTo(WebsiteSite::class); + } + + public function rules(): HasMany + { + return $this->hasMany(SitemapRule::class); + } + + public function urls(): HasMany + { + return $this->hasMany(SitemapUrl::class); + } + + public function indexFiles(): HasMany + { + return $this->hasMany(SitemapIndexFile::class); + } +} diff --git a/src/Models/SitemapRule.php b/src/Models/SitemapRule.php new file mode 100644 index 0000000..9e310dc --- /dev/null +++ b/src/Models/SitemapRule.php @@ -0,0 +1,36 @@ + 'array', + ]; + + protected $auditInclude = [ + 'sitemap_profile_id', + 'rule_type', + 'rule_data', + ]; + + public function profile(): BelongsTo + { + return $this->belongsTo(SitemapProfile::class, 'sitemap_profile_id'); + } +} diff --git a/src/Models/SitemapUrl.php b/src/Models/SitemapUrl.php new file mode 100644 index 0000000..adb049b --- /dev/null +++ b/src/Models/SitemapUrl.php @@ -0,0 +1,46 @@ + 'boolean', + 'lastmod' => 'datetime', + 'alternate_locales' => 'array', + 'priority' => 'float', + ]; + + protected $auditInclude = [ + 'url', + 'changefreq', + 'priority', + 'lastmod', + 'is_active', + 'alternate_locales', + ]; + + public function profile(): BelongsTo + { + return $this->belongsTo(SitemapProfile::class, 'sitemap_profile_id'); + } +} diff --git a/src/Models/WebsiteContent.php b/src/Models/WebsiteContent.php new file mode 100644 index 0000000..c040cf3 --- /dev/null +++ b/src/Models/WebsiteContent.php @@ -0,0 +1,194 @@ + 'array', + 'content_blocks' => 'array', + 'seo_overrides' => 'array', + 'roles' => 'array', + 'permissions' => 'array', + 'is_draft' => 'boolean', + 'is_sensitive' => 'boolean', + 'is_partial' => 'boolean', + 'hide_if_authenticated' => 'boolean', + 'hide_if_guest' => 'boolean', + 'visible_from' => 'timestamp', + 'visible_until' => 'timestamp', + 'enable_cache' => 'boolean', + 'cache_ttl' => 'integer', + ]; + + protected $auditInclude = [ + 'site_id', + 'seo_profile_id', + 'title', + 'slug', + 'description', + 'keywords', + 'template', + 'template_variant', + 'type', + 'render_mode', + 'block_mode', + 'resource', + 'render_as', + 'canonical_url', + 'content_blocks', + 'seo_overrides', + 'is_draft', + 'is_sensitive', + 'is_partial', + 'roles', + 'permissions', + 'hide_if_authenticated', + 'hide_if_guest', + 'visible_from', + 'visible_until', + 'enable_cache', + 'cache_ttl', + ]; + + // ===================== RELACIONES ===================== + + public function site() : BelongsTo + { + return $this->belongsTo(WebsiteSite::class); + } + + public function seoProfile() : BelongsTo + { + return $this->belongsTo(WebsiteSeoProfile::class, 'seo_profile_id'); + } + + public function versions() : HasMany + { + return $this->hasMany(WebsiteContentVersion::class); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->title; + } + + public function getEffectiveSeoMetadata(): array + { + $base = $this->seoProfile?->getMetaTags() ?? []; + + return array_merge($base, $this->seo_overrides ?? []); + } + + public function getCanonicalUrl(): ?string + { + return $this->canonical_url ?: ($this->seoProfile->og_url ?? null); + } + + public function toHtml(): string + { + return view('website::templates.' . ($this->template ?? 'default'), [ + 'content' => $this, + ])->render(); + } + + // ===================== SCOPES ===================== + + public function scopePublished($query) : Builder + { + return $query + ->where('is_draft', false) + ->where(function ($q) { + $q->whereNull('visible_from')->orWhere('visible_from', '<=', now()); + }) + ->where(function ($q) { + $q->whereNull('visible_until')->orWhere('visible_until', '>=', now()); + }); + } + + public function scopeBySlug($query, string $slug) : Builder + { + return $query->where('slug', $slug); + } + + public function scopeDraft($query) : Builder + { + return $query->where('is_draft', true); + } + + + // ===================== PREVIEW ===================== + + public function previewUrl(?int $userId = null, int $ttl = 30): string + { + return URL::temporarySignedRoute( + 'website.preview', + now()->addMinutes($ttl), + [ + 'slug' => $this->slug, + 'user_id' => $userId ?? Auth::id() + ] + ); + } + +} diff --git a/src/Models/WebsiteContentBlock.php b/src/Models/WebsiteContentBlock.php new file mode 100644 index 0000000..bbb457d --- /dev/null +++ b/src/Models/WebsiteContentBlock.php @@ -0,0 +1,158 @@ + 'array', + 'data' => 'array', + 'is_enabled' => 'boolean', + 'enable_cache' => 'boolean', + 'cache_ttl' => 'integer', + ]; + + protected $auditInclude = [ + 'content_id', + 'parent_id', + 'slug', + 'type', + 'mode', + 'view', + 'view_path', + 'component_class', + 'is_enabled', + 'enable_cache', + 'cache_ttl', + 'settings', + 'data', + 'order', + ]; + + + // ===================== RELACIONES ===================== + + public function content() : BelongsTo + { + return $this->belongsTo(WebsiteContent::class); + } + + public function parent() : BelongsTo + { + return $this->belongsTo(WebsiteContentBlock::class, 'parent_id'); + } + + public function versions() : HasMany + { + return $this->hasMany(WebsiteContentVersion::class); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->title; + } + + public function getEffectiveSeoMetadata(): array + { + $base = $this->seoProfile?->getMetaTags() ?? []; + + return array_merge($base, $this->seo_overrides ?? []); + } + + public function getCanonicalUrl(): ?string + { + return $this->canonical_url ?: ($this->seoProfile->og_url ?? null); + } + + public function toHtml(): string + { + return view('website::templates.' . ($this->template ?? 'default'), [ + 'content' => $this, + ])->render(); + } + + // ===================== SCOPES ===================== + + public function scopePublished($query) : Builder + { + return $query + ->where('is_draft', false) + ->where(function ($q) { + $q->whereNull('visible_from')->orWhere('visible_from', '<=', now()); + }) + ->where(function ($q) { + $q->whereNull('visible_until')->orWhere('visible_until', '>=', now()); + }); + } + + public function scopeBySlug($query, string $slug) : Builder + { + return $query->where('slug', $slug); + } + + public function scopeDraft($query) : Builder + { + return $query->where('is_draft', true); + } + + + // ===================== PREVIEW ===================== + + public function previewUrl(?int $userId = null, int $ttl = 30): string + { + return URL::temporarySignedRoute( + 'website.preview', + now()->addMinutes($ttl), + [ + 'slug' => $this->slug, + 'user_id' => $userId ?? Auth::id() + ] + ); + } + +} diff --git a/src/Models/WebsiteContentVersion.php b/src/Models/WebsiteContentVersion.php new file mode 100644 index 0000000..c4092c1 --- /dev/null +++ b/src/Models/WebsiteContentVersion.php @@ -0,0 +1,64 @@ + 'array', + ]; + + protected $auditInclude = [ + 'website_content_id', + 'version_label', + 'content', + 'metadata', + 'created_by', + ]; + + // ===================== RELACIONES ===================== + + public function content(): BelongsTo + { + return $this->belongsTo(WebsiteContent::class, 'website_content_id'); + } + + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->version_label; + } +} diff --git a/src/Models/WebsiteMenu.php b/src/Models/WebsiteMenu.php new file mode 100644 index 0000000..15cbdc8 --- /dev/null +++ b/src/Models/WebsiteMenu.php @@ -0,0 +1,72 @@ + 'boolean', + ]; + + protected $auditInclude = [ + 'site_id', + 'title', + 'slug', + 'description', + 'is_active', + ]; + + // ===================== GETTERS ===================== + + public function items(): HasMany + { + return $this->hasMany(WebsiteMenuItem::class, 'menu_id') + ->whereNull('parent_id') + ->orderBy('order'); + } + + public function getDisplayName(): string + { + return $this->title; + } + + // ===================== RELACIONES ===================== + + public function site(): BelongsTo + { + return $this->belongsTo(WebsiteSite::class); + } +} diff --git a/src/Models/WebsiteMenuItem.php b/src/Models/WebsiteMenuItem.php new file mode 100644 index 0000000..105c25b --- /dev/null +++ b/src/Models/WebsiteMenuItem.php @@ -0,0 +1,121 @@ + 'array', + 'type' => WebsiteMenuItemType::class, + 'target' => WebsiteMenuItemTarget::class, + 'roles' => 'array', + 'permissions' => 'array', + 'hide_if_authenticated' => 'boolean', + 'hide_if_guest' => 'boolean', + 'is_active' => 'boolean', + ]; + + protected $auditInclude = [ + 'title', + 'type', + 'linkable_id', + 'linkable_type', + 'laravel_route', + 'url', + 'js_event', + 'target', + 'method', + 'icon', + 'badge', + 'badge_color', + 'roles', + 'permissions', + 'hide_if_authenticated', + 'hide_if_guest', + 'visible_from', + 'visible_until', + 'order', + 'is_active', + ]; + + // ===================== RELACIONES ===================== + + public function menu(): BelongsTo + { + return $this->belongsTo(WebsiteMenu::class, 'menu_id'); + } + + public function parent(): BelongsTo + { + return $this->belongsTo(self::class, 'parent_id'); + } + + public function children(): HasMany + { + return $this->hasMany(self::class, 'parent_id')->orderBy('order'); + } + + public function linkable(): MorphTo + { + return $this->morphTo(); + } + + // ===================== GETTERS ===================== + + // Accesor para devolver título correcto según idioma actual + public function getLocalizedTitleAttribute(): string + { + $locale = app()->getLocale(); + $fallback = config('app.fallback_locale', 'es'); + + return $this->title[$locale] ?? $this->title[$fallback] ?? ''; + } +} diff --git a/src/Models/WebsiteSeoProfile.php b/src/Models/WebsiteSeoProfile.php new file mode 100644 index 0000000..5fa053a --- /dev/null +++ b/src/Models/WebsiteSeoProfile.php @@ -0,0 +1,141 @@ + WebsiteSeoProfileType::class, + 'schema_org' => 'array', + 'geo_location' => 'array', + 'json_ld' => 'array', + 'noindex' => 'boolean', + 'nofollow' => 'boolean', + ]; + + protected $auditInclude = [ + 'site_id', + 'type', + 'title', + 'slug', + 'description', + 'schema_org', + 'noindex', + 'nofollow', + 'locale', + 'geo_location', + 'og_type', + 'og_title', + 'og_description', + 'og_image', + 'og_url', + 'og_site_name', + 'twitter_card', + 'twitter_title', + 'twitter_description', + 'twitter_image', + 'twitter_site', + 'twitter_creator', + 'json_ld', + ]; + + // ===================== RELACIONES ===================== + + public function site(): BelongsTo + { + return $this->belongsTo(WebsiteSite::class); + } + + // ===================== SCOPES ===================== + + public function scopeActive($query) : Builder + { + return $query->where('noindex', false); + } + + // ===================== GETTERS ===================== + + public function getMetaTags(): array + { + return [ + 'title' => $this->title, + 'description' => $this->description, + 'robots' => ($this->noindex ? 'noindex' : 'index') . ', ' . ($this->nofollow ? 'nofollow' : 'follow'), + 'canonical' => $this->og_url ?? null, + 'og' => [ + 'type' => $this->og_type, + 'title' => $this->og_title, + 'description' => $this->og_description, + 'image' => $this->og_image, + 'url' => $this->og_url, + 'site_name' => $this->og_site_name, + ], + 'twitter' => [ + 'card' => $this->twitter_card, + 'title' => $this->twitter_title, + 'description' => $this->twitter_description, + 'image' => $this->twitter_image, + 'site' => $this->twitter_site, + 'creator' => $this->twitter_creator, + ] + ]; + } + + public function toJsonLd(): array + { + return $this->json_ld ?? []; + } +} diff --git a/src/Models/WebsiteSite.php b/src/Models/WebsiteSite.php new file mode 100644 index 0000000..15e4748 --- /dev/null +++ b/src/Models/WebsiteSite.php @@ -0,0 +1,87 @@ + WebsiteSiteStatus::class, + 'is_indexable' => 'boolean', + 'config' => 'array', + ]; + + protected $auditInclude = [ + 'name', + 'slug', + 'domain', + 'template', + 'status', + 'is_indexable', + 'seo_profile_id', + 'canonical_url', + 'config', + ]; + + // ===================== RELACIONES ===================== + + public function menus(): HasMany + { + return $this->hasMany(WebsiteMenu::class); + } + + public function contents(): HasMany + { + return $this->hasMany(WebsiteContent::class); + } + + public function seoProfiles(): BelongsTo + { + return $this->belongsTo(WebsiteSeoProfile::class); + } + + // ===================== GETTERS ===================== + + public function getDisplayName(): string + { + return $this->name; + } +} diff --git a/src/Providers/VuexyWebsiteAdminServiceProvider.php b/src/Providers/VuexyWebsiteAdminServiceProvider.php new file mode 100644 index 0000000..1c0aa69 --- /dev/null +++ b/src/Providers/VuexyWebsiteAdminServiceProvider.php @@ -0,0 +1,18 @@ +registerKonekoModule(dirname(__DIR__)); + } +} diff --git a/src/Traits/Context/HasSiteContext.php b/src/Traits/Context/HasSiteContext.php new file mode 100644 index 0000000..25bb04a --- /dev/null +++ b/src/Traits/Context/HasSiteContext.php @@ -0,0 +1,60 @@ +site = $site; + return $this; + } + + /** + * Devuelve el sitio activo desde el contexto global o el asignado + */ + public function getSite(): ?WebsiteSite + { + return $this->site ?? SiteContext::resolve(); + } + + /** + * Shortcut para obtener el ID del sitio (o null) + */ + public function getSiteId(): ?int + { + return $this->getSite()?->id; + } + + /** + * Shortcut para inyectar contexto en settings o servicios + */ + public function applySiteScopeToSettings(): static + { + if ($site = $this->getSite()) { + settings()->setScope('site', $site->id); + } + + return $this; + } + + public function applySiteScopeToCache(): static + { + if ($site = $this->getSite()) { + cache_m()->setScope('site', $site->id); + } + + return $this; + } +} diff --git a/src/Website/Cache/RenderCacheInvalidator.php b/src/Website/Cache/RenderCacheInvalidator.php new file mode 100644 index 0000000..b2b4ee8 --- /dev/null +++ b/src/Website/Cache/RenderCacheInvalidator.php @@ -0,0 +1,17 @@ +cache->forget('website.render'); + } +} \ No newline at end of file diff --git a/src/Website/Cache/WebsiteRenderCacheService.php b/src/Website/Cache/WebsiteRenderCacheService.php new file mode 100644 index 0000000..0b23301 --- /dev/null +++ b/src/Website/Cache/WebsiteRenderCacheService.php @@ -0,0 +1,74 @@ +remember($key, now()->addMinutes($ttl), $callback); + } + + /** + * Invalida un contenido renderizado + */ + public static function invalidate(string $type, string $slug): void + { + Cache::tags([ + self::TAG_MAIN, + $type, + "{$type}_{$slug}" + ])->flush(); + } + + /** + * Devuelve una respuesta HTTP renderizada con headers de depuración. + */ + public static function responseWithHeaders(string $html, string $type, string $slug): Response + { + $debug = Config::get('koneko.website.cache.html.debug_mode', false); + + return response($html)->withHeaders([ + 'X-Koneko-Cache' => 'HIT', + 'X-Koneko-Type' => $type, + 'X-Koneko-Slug' => $slug, + 'X-Koneko-TTL' => Config::get('koneko.website.cache.html.ttl.default', 900), + 'X-Koneko-Debug' => $debug ? 'true' : 'false', + ]); + } + + /** + * Limpia toda la cache HTML + */ + public static function flushAll(): void + { + Cache::tags([self::TAG_MAIN])->flush(); + Log::info('[WebsiteRenderCacheService] Toda la cache HTML ha sido invalidada.'); + } +} diff --git a/src/Website/Enums/WebsiteSiteStatus.php b/src/Website/Enums/WebsiteSiteStatus.php new file mode 100644 index 0000000..af3e160 --- /dev/null +++ b/src/Website/Enums/WebsiteSiteStatus.php @@ -0,0 +1,10 @@ + self::build($slug)); + } + + return self::build($slug); + } + + public static function clearCache(string $slug): void + { + Cache::forget(self::cacheKey($slug)); + } + + public static function clearAllCache(): void + { + $prefix = 'website_menu:'; + foreach (Cache::getRedis()->keys("{$prefix}*") as $key) { + Cache::forget(str_replace(config('cache.prefix') . ':', '', $key)); + } + } + + private static function cacheKey(string $slug): string + { + return "website_menu:{$slug}"; + } + + private static function build(string $slug): array + { + $menu = WebsiteMenu::where('slug', $slug) + ->where('is_active', true) + ->with(['items' => fn($query) => $query->where('is_active', true)->orderBy('order')]) + ->first(); + + if (!$menu) { + return []; + } + + return self::buildTree($menu->items->whereNull('parent_id'), $menu->items); + } + + private static function buildTree($items, $allItems, $parentId = null): array + { + $tree = []; + + foreach ($items as $item) { + if ($item->parent_id !== $parentId) { + continue; + } + + if (!self::canAccess($item)) { + continue; + } + + if (!self::isVisible($item)) { + continue; + } + + $meta = self::generateUrl($item); + + $tree[] = [ + 'id' => $item->id, + 'title' => $item->localized_title, + 'slug' => $item->slug, + 'url' => $item->url ?? 'javascript:;', + 'target' => $item->target ?? '_self', + 'type' => $item->type->value ?? 'custom', + 'icon' => $item->icon, + 'badge' => $item->badge, + 'badge_color' => $item->badge_color, + 'method' => $item->method ?? 'GET', + 'js_event' => $item->js_event, + 'meta' => $meta, + 'children' => self::buildTree($allItems, $allItems, $item->id), + ]; + } + + return $tree; + } + + private static function canAccess(WebsiteMenuItem $item): bool + { + return self::canAccessRole($item) && self::canAccessPermission($item); + } + + private static function canAccessRole(WebsiteMenuItem $item): bool + { + if (empty($item->roles)) { + return true; + } + + if (!Auth::check()) { + return in_array('guest', $item->roles); + } + + return Auth::user()->hasAnyRole($item->roles); + } + + private static function canAccessPermission(WebsiteMenuItem $item): bool + { + if (empty($item->permissions)) { + return true; + } + + if (!Auth::check()) { + return false; + } + + return Auth::user()->hasAnyPermission($item->permissions); + } + + private static function isVisible(WebsiteMenuItem $item): bool + { + $now = now(); + + if ($item->visible_from && $now->lt($item->visible_from)) { + return false; + } + + if ($item->visible_until && $now->gt($item->visible_until)) { + return false; + } + + return true; + } + + private static function generateUrl(WebsiteMenuItem &$item): array + { + $debugBrokenRoutes = Config::get('koneko.website.menu.debug.show_broken_routes', false); + $meta = []; + + if (!empty($item->laravel_route)) { + if (Route::has($item->laravel_route)) { + $item->url = route($item->laravel_route); + $meta['route_found'] = true; + } else { + $item->url = $debugBrokenRoutes ? 'javascript:; /* broken route */' : 'javascript:;'; + $meta['route_found'] = false; + } + } elseif (!empty($item->url)) { + $meta['route_found'] = true; + } else { + $item->url = 'javascript:;'; + $meta['route_found'] = false; + } + + return $meta; + } +} diff --git a/src/Website/Traits/Cache/HasRenderCache.php b/src/Website/Traits/Cache/HasRenderCache.php new file mode 100644 index 0000000..87ee493 --- /dev/null +++ b/src/Website/Traits/Cache/HasRenderCache.php @@ -0,0 +1,28 @@ +slug}"); + } + + public function getRenderCache(): ?string + { + return Cache::get("rendered_html.{$this->slug}"); + } + + public function setRenderCache(string $html): void + { + Cache::tags(["rendered_html", "website", "website_{$this->slug}"])->put("rendered_html.{$this->slug}", $html); + } + + public function clearRenderCache(): void + { + Cache::tags(["rendered_html", "website", "website_{$this->slug}"])->forget("rendered_html.{$this->slug}"); + } +} diff --git a/src/Website/UX/Content/WebsiteBreadcrumbsBuilderService.php b/src/Website/UX/Content/WebsiteBreadcrumbsBuilderService.php new file mode 100644 index 0000000..1cc9e7b --- /dev/null +++ b/src/Website/UX/Content/WebsiteBreadcrumbsBuilderService.php @@ -0,0 +1,107 @@ +menu = $menu ?? app(VuexyMenuBuilderService::class)->getForUser(); + } + + /** + * Devuelve el trail de breadcrumbs con "Inicio" siempre al inicio + */ + public function getBreadcrumbs(): array + { + // Si estamos en la página de inicio, no mostrar breadcrumbs + if (Route::currentRouteName() === 'admin.core.pages.home.index') { + return []; // Esto hará que $vuexyBreadcrumbs sea falsy y no se renderice el