Skip to content

Commit 266cc49

Browse files
author
TAMV Nodo Cero
committed
hardening: seed data, tanstack query hooks, edge functions deploy, wired 6 pages
1 parent 1b09b4d commit 266cc49

21 files changed

Lines changed: 3173 additions & 177 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ coverage/
3333

3434
# Supabase local
3535
supabase/.temp
36+
37+
# Backups
38+
*.bak
39+
40+
# CLI-generated migrations (originals in data/migrations/)
41+
supabase/migrations/

apps/rdm-hub/app/dashboard/observabilidad/page.tsx

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,19 @@
22

33
import { useAuth } from "@/providers/auth-provider";
44
import { useRouter } from "next/navigation";
5-
import { useEffect, useState } from "react";
6-
7-
type HealthStatus = {
8-
status: string;
9-
uptime: number;
10-
region: string;
11-
};
5+
import { useEffect } from "react";
6+
import { useHealth } from "@/hooks/use-health";
127

138
export default function ObservabilidadPage() {
14-
const { user, isLoading } = useAuth();
9+
const { user, isLoading: authLoading } = useAuth();
1510
const router = useRouter();
16-
const [health, setHealth] = useState<HealthStatus | null>(null);
17-
18-
useEffect(() => {
19-
if (!isLoading && !user) router.push("/auth");
20-
}, [user, isLoading, router]);
11+
const { data: health, isLoading } = useHealth();
2112

2213
useEffect(() => {
23-
fetch("/api/health")
24-
.then((r) => r.json())
25-
.then(setHealth)
26-
.catch(() => {});
27-
}, []);
14+
if (!authLoading && !user) router.push("/auth");
15+
}, [user, authLoading, router]);
2816

29-
if (isLoading || !user) return null;
17+
if (authLoading || !user) return null;
3018

3119
return (
3220
<div className="space-y-8">
@@ -41,12 +29,12 @@ export default function ObservabilidadPage() {
4129
<p className="text-2xl font-bold text-green-400 mt-1">{health?.status ?? "—"}</p>
4230
</div>
4331
<div className="border border-[#2a2d35] rounded-xl p-4 bg-[#121418]">
44-
<p className="text-sm text-[#9ca3af]">Uptime</p>
45-
<p className="text-2xl font-bold mt-1">{health ? `${Math.round(health.uptime)}s` : "—"}</p>
32+
<p className="text-sm text-[#9ca3af]">Base de datos</p>
33+
<p className="text-2xl font-bold mt-1">{health?.db?.connected ? "Online" : "—"}</p>
4634
</div>
4735
<div className="border border-[#2a2d35] rounded-xl p-4 bg-[#121418]">
4836
<p className="text-sm text-[#9ca3af]">Región</p>
49-
<p className="text-2xl font-bold mt-1">{health?.region ?? "—"}</p>
37+
<p className="text-2xl font-bold mt-1">{health?.supabase_region ?? "—"}</p>
5038
</div>
5139
<div className="border border-[#2a2d35] rounded-xl p-4 bg-[#121418]">
5240
<p className="text-sm text-[#9ca3af]">API</p>
@@ -72,19 +60,28 @@ export default function ObservabilidadPage() {
7260

7361
<div className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
7462
<h2 className="font-medium mb-4">Telemetría</h2>
75-
<div className="space-y-4 text-sm">
76-
{[
77-
{ metric: "Latencia API", value: "45ms" },
78-
{ metric: "Peticiones/min", value: "23" },
79-
{ metric: "Errores (24h)", value: "0" },
80-
{ metric: "Memoria", value: "128 MB" },
81-
].map((m) => (
82-
<div key={m.metric} className="flex items-center justify-between">
83-
<span className="text-[#9ca3af]">{m.metric}</span>
84-
<span className="text-[#e8e6e0] font-mono">{m.value}</span>
63+
{isLoading ? (
64+
<div className="text-[#6b7280] text-sm">Cargando...</div>
65+
) : (
66+
<div className="space-y-4 text-sm">
67+
<div className="flex items-center justify-between">
68+
<span className="text-[#9ca3af]">Latencia DB</span>
69+
<span className="text-[#e8e6e0] font-mono">{health?.db?.latency_ms ?? "—"}ms</span>
8570
</div>
86-
))}
87-
</div>
71+
<div className="flex items-center justify-between">
72+
<span className="text-[#9ca3af]">Tablas</span>
73+
<span className="text-[#e8e6e0] font-mono">{health?.db?.tables?.length ?? "—"}</span>
74+
</div>
75+
<div className="flex items-center justify-between">
76+
<span className="text-[#9ca3af]">Políticas</span>
77+
<span className="text-[#e8e6e0] font-mono">{health?.policies?.active ?? "—"} activas</span>
78+
</div>
79+
<div className="flex items-center justify-between">
80+
<span className="text-[#9ca3af]">Herramientas</span>
81+
<span className="text-[#e8e6e0] font-mono">{health?.tools?.active ?? "—"} activas</span>
82+
</div>
83+
</div>
84+
)}
8885
</div>
8986
</div>
9087
</div>

apps/rdm-hub/app/directorio/page.tsx

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
"use client";
22

3-
import { useState } from "react";
4-
5-
const categories = [
6-
"Todos", "Turismo", "Gastronomía", "Artesanías", "Hospedaje", "Servicios", "Transporte",
7-
];
8-
9-
const businesses = [
10-
{ name: "Pasteuría La Mina", cat: "Gastronomía", desc: "Los mejores pastes tradicionales" },
11-
{ name: "Hotel Real del Monte", cat: "Hospedaje", desc: "Hospedaje en el centro histórico" },
12-
{ name: "Artesanías El Sopón", cat: "Artesanías", desc: "Artesanía local en cantera" },
13-
{ name: "Restaurante El Edén", cat: "Gastronomía", desc: "Cocina tradicional hidalguense" },
14-
{ name: "Guía Turística RDM", cat: "Turismo", desc: "Recorridos guiados por el pueblo" },
15-
{ name: "Transportes RDM", cat: "Transporte", desc: "Transporte local y shuttle CDMX" },
16-
{ name: "Cabañas del Bosque", cat: "Hospedaje", desc: "Ecoturismo y cabañas" },
17-
{ name: "Museo del Paste", cat: "Gastronomía", desc: "Historia y degustación del paste" },
18-
];
3+
import { useState, useMemo } from "react";
4+
import { useNegocios } from "@/hooks/use-negocios";
195

206
export default function DirectorioPage() {
217
const [activeCat, setActiveCat] = useState("Todos");
22-
const filtered = activeCat === "Todos" ? businesses : businesses.filter((b) => b.cat === activeCat);
8+
const { data: negocios, isLoading } = useNegocios();
9+
10+
const categories = useMemo(() => {
11+
if (!negocios) return ["Todos"];
12+
const cats = [...new Set(negocios.map((b) => b.cat))];
13+
return ["Todos", ...cats];
14+
}, [negocios]);
15+
16+
const filtered = useMemo(() => {
17+
if (!negocios) return [];
18+
return activeCat === "Todos" ? negocios : negocios.filter((b) => b.cat === activeCat);
19+
}, [negocios, activeCat]);
2320

2421
return (
2522
<div className="min-h-screen">
@@ -51,15 +48,20 @@ export default function DirectorioPage() {
5148
</div>
5249
</div>
5350

54-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
55-
{filtered.map((biz) => (
56-
<div key={biz.name} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418] hover:border-[#c8a356] transition-colors">
57-
<span className="text-xs text-[#c8a356] font-medium">{biz.cat}</span>
58-
<h3 className="font-medium mt-1">{biz.name}</h3>
59-
<p className="text-sm text-[#9ca3af] mt-1">{biz.desc}</p>
60-
</div>
61-
))}
62-
</div>
51+
{isLoading ? (
52+
<div className="text-[#6b7280]">Cargando directorio...</div>
53+
) : (
54+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
55+
{filtered.map((biz) => (
56+
<div key={biz.id} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418] hover:border-[#c8a356] transition-colors">
57+
<span className="text-xs text-[#c8a356] font-medium">{biz.cat}</span>
58+
<h3 className="font-medium mt-1">{biz.name}</h3>
59+
<p className="text-sm text-[#9ca3af] mt-1">{biz.description}</p>
60+
{biz.phone && <p className="text-xs text-[#6b7280] mt-2">{biz.phone}</p>}
61+
</div>
62+
))}
63+
</div>
64+
)}
6365
</div>
6466
</div>
6567
);

apps/rdm-hub/app/economia/page.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useMemo } from "react";
4+
import { useNegocios } from "@/hooks/use-negocios";
45

56
const sections = [
67
{ id: "negocios", label: "Negocios" },
@@ -9,8 +10,22 @@ const sections = [
910
{ id: "donar", label: "Apoyar" },
1011
];
1112

13+
const plans = [
14+
{ name: "Básico", price: "Gratis", features: ["Directorio", "Mapa", "Eventos"] },
15+
{ name: "Comercio", price: "$199/mes", features: ["Perfil de negocio", "Promociones", "Estadísticas"] },
16+
{ name: "Premium", price: "$499/mes", features: ["Prioridad", "API access", "Soporte prioritario"] },
17+
];
18+
1219
export default function EconomiaPage() {
1320
const [activeSection, setActiveSection] = useState("negocios");
21+
const { data: negocios } = useNegocios();
22+
23+
const cats = useMemo(() => {
24+
if (!negocios) return []
25+
const map = new Map<string, number>()
26+
negocios.forEach((b) => map.set(b.cat, (map.get(b.cat) || 0) + 1))
27+
return Array.from(map.entries()).map(([cat, count]) => ({ cat, count }))
28+
}, [negocios])
1429

1530
return (
1631
<div className="min-h-screen">
@@ -42,14 +57,12 @@ export default function EconomiaPage() {
4257
<div className="space-y-4">
4358
<p className="text-[#9ca3af]">Portal de negocios locales. Registra tu comercio y forma parte del directorio digital.</p>
4459
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
45-
{["Turismo", "Gastronomía", "Artesanías", "Servicios", "Hospedaje", "Transporte"].map(
46-
(cat) => (
47-
<div key={cat} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
48-
<p className="font-medium">{cat}</p>
49-
<p className="text-sm text-[#9ca3af] mt-1">Negocios registrados</p>
50-
</div>
51-
),
52-
)}
60+
{cats.map(({ cat, count }) => (
61+
<div key={cat} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
62+
<p className="font-medium">{cat}</p>
63+
<p className="text-sm text-[#c8a356] mt-1">{count} negocios registrados</p>
64+
</div>
65+
))}
5366
</div>
5467
</div>
5568
)}
@@ -65,11 +78,7 @@ export default function EconomiaPage() {
6578

6679
{activeSection === "membresias" && (
6780
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
68-
{[
69-
{ name: "Básico", price: "Gratis", features: ["Directorio", "Mapa", "Eventos"] },
70-
{ name: "Comercio", price: "$199/mes", features: ["Perfil de negocio", "Promociones", "Estadísticas"] },
71-
{ name: "Premium", price: "$499/mes", features: ["Prioridad", "API access", "Soporte prioritario"] },
72-
].map((plan) => (
81+
{plans.map((plan) => (
7382
<div key={plan.name} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
7483
<h3 className="font-medium text-lg">{plan.name}</h3>
7584
<p className="text-2xl font-bold text-[#c8a356] mt-2">{plan.price}</p>

apps/rdm-hub/app/eventos/page.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
"use client";
22

3-
const events = [
4-
{ date: "15 Mar", title: "Feria del Paste", loc: "Plaza de la Constitución", type: "Gastronomía" },
5-
{ date: "20 Mar", title: "Concierto de Música de Viento", loc: "Teatro Hidalgo", type: "Cultural" },
6-
{ date: "05 Abr", title: "Recorrido Nocturno Leyendas", loc: "Centro Histórico", type: "Turismo" },
7-
{ date: "12 Abr", title: "Exposición de Artesanía", loc: "Casa de Cultura", type: "Cultural" },
8-
{ date: "01 May", title: "Feria de la Primavera", loc: "Jardín Principal", type: "Festividad" },
9-
{ date: "15 May", title: "Día del Minero", loc: "Mina de Acosta", type: "Tradición" },
10-
];
3+
import { useEventos } from "@/hooks/use-eventos";
114

125
export default function EventosPage() {
6+
const { data: eventos, isLoading } = useEventos();
7+
138
return (
149
<div className="min-h-screen">
1510
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-6">
1611
<h1 className="font-serif text-3xl font-bold">Eventos</h1>
1712
<p className="text-[#9ca3af]">Calendario cultural, turístico y social de Real del Monte.</p>
1813

19-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
20-
{events.map((ev) => (
21-
<div key={ev.title} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
22-
<div className="flex items-center justify-between mb-3">
23-
<span className="text-[#c8a356] font-bold">{ev.date}</span>
24-
<span className="text-xs px-2 py-1 rounded-full bg-[#1e2128] text-[#9ca3af]">{ev.type}</span>
14+
{isLoading ? (
15+
<div className="text-[#6b7280]">Cargando eventos...</div>
16+
) : (
17+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
18+
{eventos?.map((ev) => (
19+
<div key={ev.id} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
20+
<div className="flex items-center justify-between mb-3">
21+
<span className="text-[#c8a356] font-bold">{ev.date}</span>
22+
<span className="text-xs px-2 py-1 rounded-full bg-[#1e2128] text-[#9ca3af]">{ev.category}</span>
23+
</div>
24+
<h3 className="font-medium">{ev.title}</h3>
25+
<p className="text-sm text-[#9ca3af] mt-1">{ev.location}</p>
26+
<p className="text-xs text-[#6b7280] mt-2 line-clamp-2">{ev.description}</p>
2527
</div>
26-
<h3 className="font-medium">{ev.title}</h3>
27-
<p className="text-sm text-[#9ca3af] mt-1">{ev.loc}</p>
28-
</div>
29-
))}
30-
</div>
28+
))}
29+
</div>
30+
)}
3131
</div>
3232
</div>
3333
);

apps/rdm-hub/app/explorar/page.tsx

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { useState } from "react";
4-
import Link from "next/link";
4+
import { usePlaces } from "@/hooks/use-places";
5+
import { routes } from "@/lib/data";
56

67
const sections = [
78
{ id: "mapa", label: "Mapa" },
@@ -12,6 +13,7 @@ const sections = [
1213

1314
export default function ExplorarPage() {
1415
const [activeSection, setActiveSection] = useState("mapa");
16+
const { data: places, isLoading } = usePlaces();
1517

1618
return (
1719
<div className="min-h-screen">
@@ -41,7 +43,7 @@ export default function ExplorarPage() {
4143
<h1 className="font-serif text-3xl font-bold">Mapa del Territorio</h1>
4244
<p className="text-[#9ca3af]">Explora Real del Monte a través de capas interactivas: histórico, turístico, cultural y económico.</p>
4345
<div className="aspect-video rounded-xl border border-[#2a2d35] bg-[#121418] flex items-center justify-center">
44-
<p className="text-[#6b7280]">Mapa interactivo — placeholder</p>
46+
<p className="text-[#6b7280]">Mapa interactivo — próximamente</p>
4547
</div>
4648
</div>
4749
)}
@@ -50,20 +52,19 @@ export default function ExplorarPage() {
5052
<div className="space-y-6">
5153
<h1 className="font-serif text-3xl font-bold">Lugares</h1>
5254
<p className="text-[#9ca3af]">Descubre los puntos de interés del Pueblo Mágico.</p>
53-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
54-
{[
55-
"Museo de Medicina Laboral",
56-
"Panteón Inglés",
57-
"Parroquia de Nuestra Señora de la Asunción",
58-
"Mina de Acosta",
59-
"Plaza de la Constitución",
60-
"Museo Paste",
61-
].map((place) => (
62-
<div key={place} className="border border-[#2a2d35] rounded-xl p-4 bg-[#121418] hover:border-[#c8a356] transition-colors">
63-
<p className="font-medium">{place}</p>
64-
</div>
65-
))}
66-
</div>
55+
{isLoading ? (
56+
<div className="text-[#6b7280]">Cargando lugares...</div>
57+
) : (
58+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
59+
{places?.map((p) => (
60+
<div key={p.id} className="border border-[#2a2d35] rounded-xl p-4 bg-[#121418] hover:border-[#c8a356] transition-colors">
61+
<span className="text-xs text-[#c8a356] font-medium">{p.cat}</span>
62+
<p className="font-medium mt-1">{p.name}</p>
63+
<p className="text-sm text-[#9ca3af] mt-1 line-clamp-2">{p.description}</p>
64+
</div>
65+
))}
66+
</div>
67+
)}
6768
</div>
6869
)}
6970

@@ -72,15 +73,12 @@ export default function ExplorarPage() {
7273
<h1 className="font-serif text-3xl font-bold">Rutas y Experiencias</h1>
7374
<p className="text-[#9ca3af]">Recorridos temáticos por el territorio.</p>
7475
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
75-
{[
76-
{ name: "Ruta del Paste", desc: "Recorrido gastronómico por las pasteurías tradicionales" },
77-
{ name: "Ruta Minera", desc: "Historia viva de las minas de Real del Monte" },
78-
{ name: "Ruta Cultural", desc: "Museos, galerías y patrimonio arquitectónico" },
79-
{ name: "Ruta Ecoturística", desc: "Senderos y áreas naturales alrededor del pueblo" },
80-
].map((ruta) => (
76+
{routes.map((ruta) => (
8177
<div key={ruta.name} className="border border-[#2a2d35] rounded-xl p-6 bg-[#121418]">
82-
<h3 className="font-medium">{ruta.name}</h3>
83-
<p className="text-sm text-[#9ca3af] mt-1">{ruta.desc}</p>
78+
<span className="text-xs text-[#c8a356] font-medium">{ruta.category}</span>
79+
<h3 className="font-medium mt-1">{ruta.name}</h3>
80+
<p className="text-sm text-[#9ca3af] mt-1">{ruta.description}</p>
81+
<p className="text-xs text-[#6b7280] mt-2">{ruta.duration} · {ruta.distance} km</p>
8482
</div>
8583
))}
8684
</div>
@@ -92,7 +90,7 @@ export default function ExplorarPage() {
9290
<h1 className="font-serif text-3xl font-bold">Gemelo Digital</h1>
9391
<p className="text-[#9ca3af]">Réplica digital del territorio con datos en tiempo real.</p>
9492
<div className="aspect-video rounded-xl border border-[#2a2d35] bg-[#121418] flex items-center justify-center">
95-
<p className="text-[#6b7280]">Gemelo Digital 3D — placeholder</p>
93+
<p className="text-[#6b7280]">Gemelo Digital 3D — próximamente</p>
9694
</div>
9795
</div>
9896
)}

0 commit comments

Comments
 (0)