# Modelo de Datos — Catálogo de Servicios Base **SQLite** (`catalogo.db`), modo WAL + foreign keys ON. 3 tablas. El esquema se crea/migra solo al arrancar (`init_db()` en `server.py`); migraciones idempotentes (`ALTER TABLE` solo si la columna falta). ``` proveedores (1) ──< (N) servicios [FK proveedor_id, ON DELETE SET NULL] usuarios (login, independiente) ``` --- ## `proveedores` — Quién ofrece el servicio Touroperadores / proveedores. | Campo | Tipo | Notas | |---|---|---| | `id` | INTEGER PK | | | `nombre` | TEXT | requerido | | `tipo_principal` | TEXT | `tour` (default) / `ayb` / `transportacion` | | `contacto`, `telefono`, `email`, `sitio_web` | TEXT | datos de contacto | | `comision_default` | REAL | comisión base del proveedor | | `notas` | TEXT | internas | | `activo` | INTEGER | 1/0 | | `created_at`, `updated_at` | TEXT | timestamps | --- ## `servicios` — Fuente única de verdad La tabla central. Campos agrupados por **sección de impacto**. | Sección | Campos | |---|---| | 📋 **Identidad** | `codigo`, `tipo` (tour/ayb/transportacion), `categoria`, `nombre` (req.), `descripcion` | | ⚙️ **Operación** | `modo_disponibilidad`, `horarios` (JSON), `capacidad_min`, `capacidad_max`, `restricciones`, `atributos` (JSON por tipo) | | 💰 **Comercial** | `precio_neto`, `precio_publico`, `moneda` (MXN), `unidad` (por_persona/por_grupo/por_vehiculo/por_evento), `tarifas_adicionales` | | 📄 **Condiciones** | `terminos` | | 📍 **Reserva-lista** | `ubicacion`, `mapa_url`, `checkin`, `anticipacion` | | 🍽️ **Alimentos** | `incluye_alimentos` (0/1), `menu_detalle` | | 🌐 **Publicación** | `mostrar_en_web` (0/1) | | | `activo`, `notas`, `created_at`, `updated_at`, `proveedor_id` (FK) | ### `atributos` — JSON flexible por tipo En vez de columnas rígidas, los extras específicos van en JSON. Sugeridos (definidos en `index.html → ATRIBUTOS`): - **tour**: duracion, punto_encuentro, incluye, no_incluye, idioma - **ayb**: tipo_menu, min_personas, montaje, servicio_incluido, opciones_dieta - **transportacion**: tipo_vehiculo, pasajeros, ruta_zona, chofer, tiempo_espera ### `modo_disponibilidad` + `horarios` (JSON) Eje **independiente** del `tipo`. Default por tipo (`MODO_DEFAULT`): tour→`salidas`, transportacion→`siempre`, ayb→`por_evento`. Editable. | Modo | `horarios` | |---|---| | `salidas` | `{"Lun":["08:00","14:00"], ...}` (días + varias salidas/día) | | `rango` | `{"dias":[...],"desde":"06:00","hasta":"22:00"}` | | `siempre` (24/7) | `{}` | | `por_evento` | `{}` (reserva por fecha) | > Compatibilidad: servicios viejos sin `modo` se tratan como `salidas` con su `horarios` día-keyed. --- ## `usuarios` — Login `id`, `username` (único), `pass_hash` (PBKDF2-SHA256 + salt), `nombre`, `activo`, `created_at`. Sesión = cookie firmada con HMAC (clave en `secret.key`, NO versionada). --- ## Archivos (no en DB) En `uploads/servicio_{id}/` con prefijo: - `foto_` — fotos del servicio - `menu_` — fotos del menú/alimentos (separadas; `loadPhotos` excluye `menu_`, `loadMenuFotos` solo `menu_`) - `doc_` — documentos `/api/file-counts` elige `first_image` **prefiriendo** `foto_` y **excluyendo** docs y `menu` (para no robar el thumbnail).