1414// Patrón: useApp() + useActiveAccount() + useIsMobile(), igual que KitchenItemsPage.
1515
1616import { useEffect , useMemo , useState } from 'react'
17- import { Search , ChevronDown , ChevronRight , CircleDashed , CheckCircle2 , AlertTriangle , UtensilsCrossed , Package , Link2Off , Plus , FolderPlus , ArrowRightLeft , X , Undo2 , Info , ArrowUp , ArrowDown , Trash2 , UploadCloud , Loader2 , Sparkles , PackagePlus } from 'lucide-react'
17+ import { Search , ChevronDown , ChevronRight , CircleDashed , CheckCircle2 , AlertTriangle , UtensilsCrossed , Package , Link2Off , Link2 , Plus , FolderPlus , ArrowRightLeft , X , Undo2 , Info , ArrowUp , ArrowDown , Trash2 , UploadCloud , Loader2 , Sparkles , PackagePlus } from 'lucide-react'
1818import { useActiveAccount } from '@/modules/multitenancy/hooks/useActiveAccount'
1919import {
2020 listBrandsWithCatalog ,
@@ -34,6 +34,7 @@ import NewCategoryModal from '@/modules/kitchen/components/NewCategoryModal'
3434import type { MenuItemEconomics } from '@/types/kitchen'
3535import { publishBrandCatalog , type PublishResult } from '@/modules/kitchen/services/catalogPublishService'
3636import PublishStatusChip from '@/modules/kitchen/components/PublishStatusChip'
37+ import { connectBrandToDelivery , type ConnectResult } from '@/modules/kitchen/services/hubriseBrandConnectService'
3738
3839function formatEur ( value : number | null ) : string {
3940 if ( value === null || value === undefined ) return '—'
@@ -80,6 +81,8 @@ export default function KitchenMenuPage() {
8081 const [ publishing , setPublishing ] = useState ( false )
8182 const [ publishResult , setPublishResult ] = useState < PublishResult | null > ( null )
8283 const [ publishStatusKey , setPublishStatusKey ] = useState ( 0 )
84+ const [ connecting , setConnecting ] = useState ( false )
85+ const [ connectResult , setConnectResult ] = useState < ConnectResult | null > ( null )
8386
8487 // Cargar marcas con catálogo
8588 useEffect ( ( ) => {
@@ -242,6 +245,24 @@ export default function KitchenMenuPage() {
242245 }
243246 }
244247
248+ // ── Conectar la marca a delivery (Fase 2, self-service) ───────────────────
249+ // Crea/reusa el catálogo HubRise por local (sin bridge) y publica la carta.
250+ async function handleConnect ( ) {
251+ if ( ! selectedBrand || connecting ) return
252+ setConnecting ( true )
253+ setConnectResult ( null )
254+ setError ( null )
255+ try {
256+ const res = await connectBrandToDelivery ( selectedBrand . id )
257+ setConnectResult ( res )
258+ setPublishStatusKey ( ( k ) => k + 1 )
259+ } catch ( e : unknown ) {
260+ setError ( e instanceof Error ? e . message : String ( e ) )
261+ } finally {
262+ setConnecting ( false )
263+ }
264+ }
265+
245266 // ── Capa 1: organizar la carta (mover/recategorizar) ──────────────────────
246267
247268 // Categoría actual de cada producto (para poder deshacer un movimiento).
@@ -508,6 +529,17 @@ export default function KitchenMenuPage() {
508529 { selectedBrand . catalogSource === 'folvy' && activeAccountId && (
509530 < PublishStatusChip accountId = { activeAccountId } brandId = { selectedBrand . id } refreshKey = { publishStatusKey } />
510531 ) }
532+ { selectedBrand . catalogSource === 'folvy' && (
533+ < button
534+ onClick = { handleConnect }
535+ disabled = { connecting }
536+ className = "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg font-medium border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 disabled:opacity-50"
537+ title = "Crear/reusar el catálogo de esta marca en HubRise y publicar la carta (sin bridge)"
538+ >
539+ { connecting ? < Loader2 className = "w-4 h-4 animate-spin" /> : < Link2 className = "w-4 h-4" /> }
540+ { connecting ? 'Conectando…' : 'Conectar a delivery' }
541+ </ button >
542+ ) }
511543 { selectedBrand . catalogSource === 'folvy' && (
512544 < button
513545 onClick = { handlePublish }
@@ -888,6 +920,68 @@ export default function KitchenMenuPage() {
888920 </ div >
889921 ) }
890922
923+ { connectResult && (
924+ < div className = "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4" onClick = { ( ) => setConnectResult ( null ) } >
925+ < div className = "bg-white rounded-xl shadow-lg w-full max-w-lg border border-gray-200" onClick = { ( e ) => e . stopPropagation ( ) } >
926+ < div className = "px-5 py-3.5 border-b border-gray-200 flex items-center gap-2" >
927+ { connectResult . ok
928+ ? < CheckCircle2 className = "w-5 h-5 text-green-600" />
929+ : < AlertTriangle className = "w-5 h-5 text-red-600" /> }
930+ < h3 className = "text-base font-medium text-gray-900" >
931+ { connectResult . ok ? 'Marca conectada a delivery' : 'No se pudo conectar' }
932+ </ h3 >
933+ </ div >
934+ < div className = "px-5 py-4 text-sm text-gray-700 space-y-3 max-h-[60vh] overflow-auto" >
935+ { connectResult . error && < p className = "text-red-700" > { connectResult . error } </ p > }
936+ { connectResult . locations . length > 0 && (
937+ < div >
938+ < div className = "text-xs font-medium text-gray-500 mb-1" > Por local</ div >
939+ < ul className = "space-y-1" >
940+ { connectResult . locations . map ( ( l , i ) => (
941+ < li key = { i } className = "flex items-start gap-2" >
942+ { l . status === 'error'
943+ ? < AlertTriangle className = "w-4 h-4 text-red-600 mt-0.5 shrink-0" />
944+ : < CheckCircle2 className = "w-4 h-4 text-green-600 mt-0.5 shrink-0" /> }
945+ < span >
946+ < span className = "font-medium" > { l . location_name } </ span >
947+ { ' · ' }
948+ { l . status === 'ya_conectada' && 'ya conectada' }
949+ { l . status === 'creada' && 'catálogo creado' }
950+ { l . status === 'reusada_por_nombre' && 'catálogo reusado' }
951+ { l . status === 'error' && 'error' }
952+ { l . external_catalog_id && (
953+ < span className = "text-gray-400" > ({ l . external_catalog_id } )</ span >
954+ ) }
955+ { l . status === 'error' && l . error && (
956+ < span className = "block text-xs text-red-600" > { l . error } </ span >
957+ ) }
958+ </ span >
959+ </ li >
960+ ) ) }
961+ </ ul >
962+ </ div >
963+ ) }
964+ { connectResult . publish && (
965+ < div >
966+ < div className = "text-xs font-medium text-gray-500 mb-1" > Publicación de la carta</ div >
967+ { connectResult . publish . ok ? (
968+ < p className = "text-gray-600" >
969+ { connectResult . publish . products ?? 0 } producto{ ( connectResult . publish . products ?? 0 ) === 1 ? '' : 's' } · { connectResult . publish . deals ?? 0 } combo{ ( connectResult . publish . deals ?? 0 ) === 1 ? '' : 's' }
970+ </ p >
971+ ) : (
972+ < p className = "text-red-700" > { connectResult . publish . error ?? 'No se pudo publicar la carta.' } </ p >
973+ ) }
974+ </ div >
975+ ) }
976+ </ div >
977+ < div className = "flex items-center justify-end gap-2 px-5 py-3.5 border-t border-gray-200 bg-gray-50 rounded-b-xl" >
978+ < button onClick = { ( ) => setConnectResult ( null ) }
979+ className = "px-3.5 py-1.5 text-sm rounded-lg font-medium bg-accent text-text-on-accent hover:opacity-90" > Entendido</ button >
980+ </ div >
981+ </ div >
982+ </ div >
983+ ) }
984+
891985 { showNewCategory && activeAccountId && selectedBrand && (
892986 < NewCategoryModal
893987 accountId = { activeAccountId }
0 commit comments