Prototipo YB01 #1
@@ -7,6 +7,14 @@
|
||||
<meta name="description" content="Directorio colaborativo de negocios por toda España" />
|
||||
<!-- No indexar el admin -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<!-- PWA -->
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="theme-color" content="#8B0000" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="LocalESP" />
|
||||
<!-- Tabler Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.11.0/dist/tabler-icons.min.css" />
|
||||
<style>
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
command = "npm run build"
|
||||
publish = "dist"
|
||||
|
||||
# SPA: todas las rutas van al index.html (React gestiona /admin y /movil)
|
||||
# SPA: todas las rutas van al index.html (React gestiona /admin; el resto es
|
||||
# la PWA principal, instalable en móvil)
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
|
||||
+29
-8
@@ -11,10 +11,11 @@ db.exec(`
|
||||
id TEXT PRIMARY KEY,
|
||||
nombre TEXT NOT NULL,
|
||||
descripcion TEXT,
|
||||
ubicacion TEXT,
|
||||
telefono TEXT,
|
||||
email TEXT,
|
||||
web TEXT,
|
||||
provincia TEXT,
|
||||
subcategoria TEXT,
|
||||
direccion TEXT,
|
||||
enlaceGoogleMaps TEXT,
|
||||
puntuacion INTEGER DEFAULT 0,
|
||||
categoria TEXT,
|
||||
fecha TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
lat REAL,
|
||||
@@ -25,10 +26,11 @@ db.exec(`
|
||||
id TEXT PRIMARY KEY,
|
||||
nombre TEXT NOT NULL,
|
||||
descripcion TEXT,
|
||||
ubicacion TEXT,
|
||||
telefono TEXT,
|
||||
email TEXT,
|
||||
web TEXT,
|
||||
provincia TEXT,
|
||||
subcategoria TEXT,
|
||||
direccion TEXT,
|
||||
enlaceGoogleMaps TEXT,
|
||||
puntuacion INTEGER DEFAULT 0,
|
||||
categoria TEXT,
|
||||
estado TEXT DEFAULT 'pendiente',
|
||||
fechaPropuesta TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -49,4 +51,23 @@ db.exec(`
|
||||
);
|
||||
`);
|
||||
|
||||
// Migración suave: si la base de datos ya existía con el esquema antiguo
|
||||
// (sin provincia/subcategoria/direccion/enlaceGoogleMaps/puntuacion),
|
||||
// añadimos las columnas que falten sin borrar nada, porque
|
||||
// CREATE TABLE IF NOT EXISTS no modifica tablas ya creadas.
|
||||
function ensureColumn(table, column, definition) {
|
||||
const cols = db.prepare(`PRAGMA table_info(${table})`).all().map(c => c.name);
|
||||
if (!cols.includes(column)) {
|
||||
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const table of ["locales", "pendientes"]) {
|
||||
ensureColumn(table, "provincia", "TEXT");
|
||||
ensureColumn(table, "subcategoria", "TEXT");
|
||||
ensureColumn(table, "direccion", "TEXT");
|
||||
ensureColumn(table, "enlaceGoogleMaps", "TEXT");
|
||||
ensureColumn(table, "puntuacion", "INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
export default db;
|
||||
|
||||
+8
-8
@@ -24,16 +24,16 @@ app.get("/api/pendientes", (_req, res) => {
|
||||
|
||||
// Enviar propuesta
|
||||
app.post("/api/propuestas", (req, res) => {
|
||||
const { nombre, descripcion, ubicacion, telefono, email, web, categoria, lat, lng } = req.body;
|
||||
const { nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion, categoria, lat, lng } = req.body;
|
||||
const id = randomUUID();
|
||||
const fechaPropuesta = new Date().toISOString();
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO pendientes (id, nombre, descripcion, ubicacion, telefono, email, web, categoria, estado, fechaPropuesta, lat, lng)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pendiente', ?, ?, ?)
|
||||
`).run(id, nombre, descripcion, ubicacion, telefono, email, web, categoria, fechaPropuesta, lat, lng);
|
||||
INSERT INTO pendientes (id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion, categoria, estado, fechaPropuesta, lat, lng)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pendiente', ?, ?, ?)
|
||||
`).run(id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion ?? 0, categoria, fechaPropuesta, lat, lng);
|
||||
|
||||
res.status(201).json({ id, nombre, descripcion, ubicacion, telefono, email, web, categoria, estado: "pendiente", fechaPropuesta, lat, lng });
|
||||
res.status(201).json({ id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion: puntuacion ?? 0, categoria, estado: "pendiente", fechaPropuesta, lat, lng });
|
||||
});
|
||||
|
||||
// Aprobar propuesta
|
||||
@@ -47,9 +47,9 @@ app.post("/api/aprobar/:id", (req, res) => {
|
||||
const fecha = new Date().toISOString();
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO locales (id, nombre, descripcion, ubicacion, telefono, email, web, categoria, fecha, lat, lng)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(localeId, propuesta.nombre, propuesta.descripcion, propuesta.ubicacion, propuesta.telefono, propuesta.email, propuesta.web, propuesta.categoria, fecha, propuesta.lat, propuesta.lng);
|
||||
INSERT INTO locales (id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion, categoria, fecha, lat, lng)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(localeId, propuesta.nombre, propuesta.descripcion, propuesta.provincia, propuesta.subcategoria, propuesta.direccion, propuesta.enlaceGoogleMaps, propuesta.puntuacion ?? 0, propuesta.categoria, fecha, propuesta.lat, propuesta.lng);
|
||||
|
||||
db.prepare("DELETE FROM pendientes WHERE id = ?").run(id);
|
||||
|
||||
|
||||
+10
-2
@@ -1,7 +1,6 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.jsx";
|
||||
import AppMovil from "./AppMovil.jsx";
|
||||
import AdminPanel from "./AdminPanel.jsx";
|
||||
|
||||
// Enrutador mínimo sin dependencias extra
|
||||
@@ -9,10 +8,19 @@ const ruta = window.location.pathname.replace(/\/$/, "");
|
||||
|
||||
function Root() {
|
||||
if (ruta === "/admin") return <AdminPanel />;
|
||||
if (ruta === "/movil") return <AppMovil />;
|
||||
return <App />;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")).render(
|
||||
<StrictMode><Root /></StrictMode>
|
||||
);
|
||||
|
||||
// Registro del service worker: convierte la web en una PWA instalable
|
||||
// (icono en pantalla de inicio, apertura en modo standalone, shell offline).
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/sw.js").catch((err) => {
|
||||
console.error("Error registrando el service worker:", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user