Art4Hotel Hub: código + documentación extensiva

ERP a la medida (Python stdlib + SQLite + vanilla JS SPA).
Incluye server.py, index.html, utilidades y documentación:
README, MODELO_DATOS, API, INSTALACION, CONTEXTO, NEGOCIO,
WEB, ONBOARDING, VALOR_SISTEMA, CLAUDE.

Secretos y datos (art4hotel.db, secret.key, ACCESOS.html,
uploads/, backups/) excluidos vía .gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-06-09 00:10:07 -07:00
commit c2ae140078
16 changed files with 12675 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
# ── Secretos y datos (NUNCA subir) ──
# clave de firma de sesiones (login)
secret.key
# base de datos con datos del negocio
art4hotel.db
art4hotel.db-wal
art4hotel.db-shm
# respaldos de la DB
*.bak_*
# tarjeta con contraseñas
ACCESOS.html
# ── Archivos generados / temporales ──
uploads/
backups/
__pycache__/
*.pyc
*.log
# ── Sistema ──
.DS_Store
Thumbs.db

73
API.md Normal file
View File

@@ -0,0 +1,73 @@
# API REST — Art4Hotel Hub
Servidor: `server.py` (Python stdlib `http.server`), puerto **4401**.
Respuestas en JSON. Autenticación por **cookie de sesión** (firmada HMAC).
---
## Autenticación
Todas las rutas requieren sesión **excepto** las públicas de auth. Sin sesión: las páginas HTML muestran el login; las rutas `/api/*` devuelven `401`.
| Método | Ruta | Descripción |
|---|---|---|
| GET | `/api/needs-setup` | ¿Primera vez? → `{needs_setup: bool}` (pública) |
| POST | `/api/setup` | Crea la cuenta admin inicial (solo si no hay usuarios). Body: `{username, password, nombre}` |
| POST | `/api/login` | Inicia sesión. Body: `{username, password}` → set-cookie de sesión |
| POST | `/api/logout` | Cierra sesión (borra cookie) |
- Contraseñas: hash **PBKDF2-SHA256** (200k iteraciones + salt).
- Sesión: cookie `a4h_session` firmada con HMAC (clave en `secret.key`), válida 14 días.
---
## CRUD genérico
Un solo handler maneja todas las tablas vía el dict `TABLES` en `server.py`:
| Método | Ruta | Descripción |
|---|---|---|
| GET | `/api/{tabla}` | Lista todos los registros |
| POST | `/api/{tabla}` | Crea un registro (body = campos) |
| PUT | `/api/{tabla}/{id}` | Actualiza un registro |
| DELETE | `/api/{tabla}/{id}` | Elimina un registro |
Tablas disponibles: `ordenes`, `oc`, `productos`, `proyectos`, `propuestas`, `catalogos`, `clientes`, `trabajos`, `modelos`, `materiales`, `inventario`, `compras`, `tareas`, `bitacora`.
El dict define por tabla: `fields`, `int_fields`, `float_fields`, `nullable_fields` (para casteo/validación automática).
---
## Endpoints especiales
| Método | Ruta | Descripción |
|---|---|---|
| GET | `/api/dashboard` | KPIs del dashboard (stages, clientes activos, alertas) |
| GET | `/api/ventas` | Analítica de ventas: comparativo mensual, top clientes, tiempos de ciclo, pricing por producto |
| GET | `/api/entregas` | Pedidos entregados (agrupables por fecha/cliente/OC) |
| GET | `/api/oc` | OCs con sus líneas (`lineas`), totales y `progress` agregado |
| POST | `/api/oc-split/{oc_id}` | Entrega parcial: crea OC hermana con pedidos seleccionados |
| GET | `/api/file-counts` | `{entidad: {count, first_image}}` para TODAS las carpetas en 1 request. `first_image` prefiere fotos y excluye documentos |
| POST | `/api/upload/{id}?tipo=X&label=Y` | Sube archivo(s). `tipo` define el prefijo; `label` opcional como parte descriptiva del nombre |
| GET | `/api/files/{id}` | Lista archivos de una entidad |
| DELETE | `/api/files/{id}/{nombre}` | Elimina un archivo |
| POST | `/api/backup-now` | Dispara el respaldo en segundo plano |
| GET | `/api/backups` | Lista los respaldos disponibles |
---
## Servir contenido
| Ruta | Sirve |
|---|---|
| `/` o `/index.html` | La SPA (frontend completo) |
| `/uploads/{entidad}/{archivo}` | Archivos subidos (requiere sesión) |
---
## Notas de implementación
- **IDs legibles** (orden_id, oc_id) se generan con `MAX(num)+1`, no count — evita colisiones tras borrados.
- **Validación de unicidad** en `orden_id` al insertar (devuelve 409 si existe).
- **CORS**: headers `Access-Control-Allow-*` habilitados; `OPTIONS` responde preflight.
- Sin dependencias externas: todo con `http.server`, `sqlite3`, `json`, `urllib`, `hashlib`, `hmac` (stdlib).

97
CLAUDE.md Normal file
View File

@@ -0,0 +1,97 @@
# Art4Hotel Hub — Índice y Contexto para Claude
> Punto de entrada. Aquí está el mapa de TODO: documentación, sitios/servicios y accesos.
> Última actualización: 2026-06-06
---
## 📚 ÍNDICE DE DOCUMENTACIÓN (qué hay y dónde)
| Documento | Cubre | Léelo cuando… |
|---|---|---|
| **[NEGOCIO.md](./NEGOCIO.md)** | Contexto del negocio: clientes, productos, flujo real, equipo, reglas | …antes de cualquier cambio (la app refleja el negocio) |
| **[CONTEXTO.md](./CONTEXTO.md)** | Estado técnico del Hub: stack, modelo de datos, producto unificado, catálogos, galería de ejemplos, archivos, ventas | …vas a tocar código del Hub |
| **[WEB.md](./WEB.md)** | Sitio público art4hotel.com + `sync_catalogo.py` (Hub → web) | …trabajas en la página pública o el sync |
| **[ONBOARDING.md](./ONBOARDING.md)** | Handover: acceso, arquitectura, tabs del SPA, fórmula de margen | …repaso general rápido |
| **[VALOR_SISTEMA.html](./VALOR_SISTEMA.html)** | Valor/costo del sistema, hosting, esquema (para presentar a terceros) | …explicar el sistema o cotizarlo a alguien |
| **[ACCESOS.html](./ACCESOS.html)** | Tarjeta de accesos de usuario final (links de servicios) | …recordar cómo entrar a cada servicio |
---
## 🌐 MAPA DE SITIOS / SERVICIOS (servidor "iclaude")
Todo corre en el servidor **iclaude** (`192.168.50.46`, una Surface vieja). Acceso por **Tailscale** (red privada) salvo lo marcado público.
### Proyectos web (cada uno su propio puerto, todos del usuario)
Cada sitio tiene acceso **directo por Tailscale** (`iclaude:PUERTO`, requiere Tailscale activo) y, si está en Funnel, también una **URL pública** (desde cualquier lado, sin Tailscale).
| Puerto | Sitio | Acceso privado (Tailscale) | Acceso público (Funnel) | Notas |
|---|---|---|---|---|
| **4401** | **Art4Hotel Hub** (este proyecto) | `http://iclaude:4401` | — (privado) | Login (usuario `claude`). Operaciones/ventas/catálogo |
| **4402** | **Airbnb Pricing** | `http://iclaude:4402` | — (privado) | Panel pricing Airbnb (Hacienda Cabo Bello / Casa Montoya) |
| **4403** | **Catálogo** | `http://iclaude:4403` | `https://iclaude.tail69ab9b.ts.net:8443/` 🌍 | Login. Público vía Funnel |
| **4404** | **Portafolio** | `http://iclaude:4404` | `https://iclaude.tail69ab9b.ts.net/` 🌍 | Público vía Funnel (raíz) |
**Cada proyecto tiene su carpeta y su propia documentación en el servidor:**
| Proyecto | Carpeta | Servicio systemd | Su doc |
|---|---|---|---|
| Hub | `/mnt/iclaude/art4hotel-hub/` | `art4hotel-hub.service` | este `CLAUDE.md` + los .md de abajo |
| Airbnb Pricing | `/mnt/iclaude/airbnb-pricing/` | `airbnb-pricing.service` | `NEGOCIO.md` |
| Catálogo | `/mnt/iclaude/catalogo-borrador/` | `catalogo-borrador.service` | `CLAUDE.md` |
| Portafolio/Foto Studio | `/mnt/iclaude/foto-studio/` | `foto-studio.service` | `CLAUDE.md` |
Todos comparten el mismo stack (Python stdlib + SQLite + vanilla JS) y se reinician con `sudo systemctl restart <servicio>`.
### Servicios de nube personal (Docker)
| Servicio | Puerto | Acceso | Usuario |
|---|---|---|---|
| **Immich** (fotos) | 2283 | `iclaude:2283` | `claudeandrefg@gmail.com` |
| **Filebrowser** (archivos) | 8085 | `iclaude:8085` | `admin` (carpeta `/mnt/iclaude/escritorio`) |
### Sitio público externo
| Sitio | Dónde | Notas |
|---|---|---|
| **art4hotel.com** | GitHub Pages (NO en el servidor) | Landing + catálogo + wizard de leads. Repo `Claudeandrefg/art4hotel`. Se alimenta del Hub vía `sync_catalogo.py` |
---
## 🔧 INFRAESTRUCTURA (claves del servidor)
- **Acceso admin**: `ssh claude@192.168.50.46` (o `claude@iclaude` por Tailscale). Usuario `claude`, sudo sin password, llave SSH.
- **Tailscale**: red privada. MagicDNS activo → `iclaude` resuelve a `100.110.177.1`. Tailnet: `tail69ab9b.ts.net`. Dispositivos: iphone, macbook, pixel.
- **Tailscale Funnel**: expone a internet `:443 → 4404 (Portafolio)` y `:8443 → 4403 (Catálogo)`.
- **⏰ Reloj**: la Surface tiene CMOS muerta (RTC=2009) y NTP bloqueado. `sync_clock.sh` (cron @reboot + cada hora) lo corrige desde IPs fijas (1.1.1.1). **Si los logins fallan con "logged out due to inactivity" → revisar el reloj** (`date`); era el bug raíz.
- **DNS**: Tailscale ya NO gestiona el DNS (`--accept-dns=false`); usa 1.1.1.1/8.8.8.8. Resuelve nombres públicos OK.
- **RAM**: solo 3.7GB (sin ampliar). Apagados Odoo/NocoDB/Jellyfin/qBittorrent/pihole para liberar. NO instalar plataformas pesadas (n8n, etc.) — usar scripts ligeros (Python stdlib/cron).
- **Backups**: cron diario medianoche → `backup.py` (DB + uploads, 30 días en `backups/`).
---
## ⚠️ Principio clave: producto = fuente única de verdad
Cada atributo de producto impacta hasta 3 funciones. Antes de agregar/cambiar uno, define su impacto en: **(1) Operación/producción · (2) Catálogo/cotizador · (3) Página web**.
## Deploy del Hub
- Host: `claude@192.168.50.46` · Path: `/mnt/iclaude/art4hotel-hub/`
- Restart: `sudo systemctl restart art4hotel-hub`
- Deploy: `scp index.html server.py claude@192.168.50.46:/mnt/iclaude/art4hotel-hub/ && ssh claude@192.168.50.46 "sudo systemctl restart art4hotel-hub"`
## Convenciones de edición
- Toda edición a `index.html` o `server.py` se hace LOCAL aquí y se despliega con scp.
- Después de cualquier cambio en DB (ALTER TABLE, migración), verificar con SSH que se aplicó.
- No instalar dependencias nuevas — stack es Python stdlib + vanilla JS. Si una feature lo requiere, discutir antes.
- Cambios masivos (renombrar IDs, reorganizar tablas) → backup primero: `cp art4hotel.db art4hotel.db.bak_<fecha>`.
- Listas de personalización: editar la tabla `trabajos` en DB, no hardcodear en index.html. Hay `TRABAJO_OPTS` como fallback + `trabajoOpts()` helper que prefiere `S.trabajos` dinámico.
## Tareas frecuentes
- **Ver duplicados**: `python3 -c "import sqlite3; c=sqlite3.connect('art4hotel.db'); print(list(c.execute('SELECT orden_id,COUNT(*) FROM ordenes GROUP BY orden_id HAVING COUNT(*)>1')))"`
- **Backup manual**: `cp art4hotel.db art4hotel.db.bak_$(date +%Y%m%d_%H%M%S)`
- **Logs del servicio**: `sudo journalctl -u art4hotel-hub -n 50 --no-pager`
## Si Clod te pide algo
- Si es feature nueva → propón primero antes de implementar (usar AskUserQuestion).
- Si es fix → identifica la causa raíz, confírmala con el código y datos, después arregla.
- Si es cambio cosmético → directo, sin overthink.

93
CONTEXTO.md Normal file
View File

@@ -0,0 +1,93 @@
# Art4Hotel Hub — Contexto del proyecto
Sistema interno de operaciones para Clod (bolsas/accesorios custom, Los Cabos).
Última actualización mayor: 2026-05-31
## Stack
- **Backend**: Python 3 stdlib HTTP server + SQLite (WAL, FK on)
- **Frontend**: SPA vanilla JS, sin frameworks (`index.html` ~9000 líneas)
- **Deploy**: `claude@192.168.50.46:/mnt/iclaude/art4hotel-hub/` puerto 4401
- **Acceso**: red local `192.168.50.46:4401` · Tailscale `100.110.177.1:4401`
- **Deploy cmd**: `scp index.html server.py claude@192.168.50.46:/mnt/iclaude/art4hotel-hub/ && ssh claude@192.168.50.46 "sudo systemctl restart art4hotel-hub"`
## Modelo de datos (SQLite)
`ordenes` (pedidos/líneas de producción), `oc` (orden de compra que agrupa pedidos), `productos` (catálogo), `proyectos` (recetas recurrentes), `propuestas` (cotizaciones), `catalogos` (presentaciones PDF), `clientes`, `trabajos`, `modelos`, `materiales`, `inventario`, `tareas`, `bitacora`.
CRUD genérico: dict `TABLES` en server.py define fields/int_fields/float_fields/nullable_fields por tabla; un solo handler maneja POST/PUT/DELETE de todas.
## Conceptos clave
- **Pedido** (`ordenes`) = línea de producción individual
- **Orden (OC)** (`oc`) = agrupa pedidos, lleva factura/cobranza
- **Proyecto recurrente** (`proyectos`) = receta autorizada (cliente+producto+trabajo+logo)
- **Propuesta** → al aceptarse, botón "Convertir a Orden" crea OC + N pedidos (`oc.propuesta_id`)
- **tipo_orden**: OC / Resurtido / Muestra / Defecto / Faltante
- **Bodega**: *Con orden* (hoteles, facturable) vs *Sin orden* (resurtido/POS libre)
- **Stages**: Nuevo → En 2 Mares / En Taller Sofía → En Almacén → En Vehículo → Entregado
## Producto — modelo unificado (editor de 4 secciones)
El producto es la **fuente única de verdad**. Editor `openProductoEdit` organizado por impacto:
- **📋 Identidad** (catálogo·web·cotizador): nombre, categoría, talla, medidas, material, `descripcion_web`
- **🎨 Personalización** (cotizador·web): `tipos_trabajo_disponibles` (CSV multi-select) — al cotizar SOLO aparecen estos
- **⚙️ Operación** (producción·inventario): sku (no editable), costo_base, proveedor, stock_actual, punto_reorden
- **🌐 Publicación**: `mostrar_en_web`
Quickview `openProductoView` = visual read-only + botón Editar (ARRIBA de la galería) + galería de ejemplos.
### ⚠️ PRINCIPIO: cada atributo de producto impacta hasta 3 funciones
Al agregar/cambiar un atributo de producto, **definir su impacto en las 3 antes de implementar**:
1. **Operación / flujo de producción** (kanban, inventario, costos)
2. **Catálogo / cotizador** (propuestas, catálogo PDF)
3. **Página web** (sync a art4hotel.com)
Ejemplo: agregar "uso" (boda/empresa/hotel) → afecta filtros web + tal vez segmentación de cotización.
### Campos legacy (en DB, retirados del editor)
`color` (datos sucios), `logo_diseno` (pertenece a proyecto/pedido), `modelo` (vacío), `tipo_personalizacion` (ya migrado → `tipos_trabajo_disponibles`). Pendiente: limpieza con script.
## Galería de ejemplos (base + personalización)
- Un producto base muestra "cómo se ha personalizado" = pedidos (`ordenes`) con foto cuyo `producto` coincide por nombre.
- Curaduría por ejemplo: `ordenes.web_ejemplo` (1=va a web) + `ordenes.web_etiqueta` (etiqueta PÚBLICA: zona/"muestra", **nunca el cliente real** — protege la cartera).
- `getEjemplosDeProducto(nombre)` calcula en cliente desde S.ordenes + fileIndex.
## Archivos / fotos
- Guardados en `uploads/{entidad}/`, prefijo del tipo en el nombre: `factura_`, `foto_avance_produccion_`, `foto_producto_base_`, `recibo_entrega_`, etc.
- **El prefijo es crítico**: `/api/file-counts` lo usa para elegir `first_image` PREFIRIENDO fotos (`foto_`/`mockup`) y EXCLUYENDO documentos (`factura`,`recibo`,`comprobante`,`soporte`,`contrato`,`propuesta`).
- Subida con **etiqueta opcional**: `?tipo=X&label=...` → nombre `{tipo}_{ts}_{label}` (prefijo siempre se conserva). Sugerencia automática según tipo+contexto (`suggestFileLabel`).
## Catálogos (Propuestas → 📚 Catálogos)
- Tabla `catalogos`: nombre, segmento, cliente_nombre, fecha, items(JSON), show_prices/show_clientes/show_contacto, entrega, minimo_compra, terminos, status.
- Builder: picker de Productos / Proyectos / Pedidos-con-foto. Items guardan snapshot + **rehidratación** (`catRehydrateItem`) al abrir → refresca datos vivos del producto, conserva `precio_unit` manual.
- Precio unitario **editable inline** por item (vacío = no se muestra).
- PDF: portada (logo oficial centrado) → grids 2 productos/página → pie con términos comerciales (entrega·mínimo) + contacto toggleable en cada página.
- Imprimir: `catPrintCatalog()` agrega clase `cat-printing` al body → `@media print` A4 vertical.
## Ventas (tab unificada)
Sub-vistas: 📊 Dashboard (KPIs, comparativo mes/mes-pasado/año-pasado, top clientes, tiempos de ciclo, pricing por producto) · 📋 Por OC (kanban cobranza) · 📦 Por Entregas (panel "En Vehículo" + histórico).
## Marca / estilo UI
- Fuentes: **Outfit** (cuerpo) + **Playfair Display Italic** (display) + **DM Sans** (labels)
- Colores: olivo `#5C6B4F`, olivo-osc `#3D4A33`, café `#6B4F3C`, arena `#D4C5A9`, crema `#FAF7F0`
- Constantes canónicas: `TRABAJO_OPTS`, `CONDICIONES_PAGO_OPTS`, `PRODUCTO_CATEGORIAS`
- Nav: tabs en desktop, dropdown en móvil (≤700px)
## Optimizaciones
- `/api/file-counts` batched (1 request) con first_image inteligente
- Imágenes `loading="lazy" decoding="async"`, kanban/bodega sin thumbs por default
- IDs con `MAX(num)+1` (no count) — evita colisiones
## Endpoints
- `GET/POST/PUT/DELETE /api/{table}`
- `GET /api/file-counts``{entity: {count, first_image}}`
- `GET /api/ventas` → dashboard analytics
- `POST /api/upload/{id}?tipo=X&label=Y` · `GET /api/files/{id}` · `DELETE /api/files/{id}/{name}`
## Backup
- `backup.py` por cron: snapshot DB (online backup, safe c/ WAL) + `uploads.tar.gz`, 30 días en `backups/`
- Backups manuales antes de migraciones: `cp art4hotel.db art4hotel.db.bak_<motivo>_<fecha>`
## Pendientes (al 2026-05-31)
- **Web**: filtros por uso (boda/empresa/tienda/hotel — requiere atributo "uso") + secciones por tipo · foto base del modal de producto web recorta en cuadrado · analytics
- **Limpieza DB**: retirar columnas legacy (color, logo_diseno, modelo, tipo_personalizacion)
- **Operación**: limpieza de campos de pedido poco usados · distinguir 3 paths visualmente
## Sitio web público → ver WEB.md
art4hotel.com (GitHub Pages) se alimenta del Hub vía `sync_catalogo.py`. Detalle en `WEB.md`.

122
INSTALACION.md Normal file
View File

@@ -0,0 +1,122 @@
# Instalación y Despliegue — Art4Hotel Hub
Sin Docker, sin pip, sin CI/CD. Solo Python 3 y `systemd`.
---
## Requisitos
- **Python 3.8+** (stdlib únicamente — `http.server`, `sqlite3`, `hashlib`, `hmac`)
- Linux con `systemd` (para correr como servicio). En Windows/Mac corre igual con `python3 server.py`.
---
## 1. Desarrollo local
```bash
git clone https://git.consultoria-as.com/consultoria-as/art4hotel-hub.git
cd art4hotel-hub
python3 server.py
# → http://localhost:4401
```
Primera vez: la app pide **crear la cuenta admin** (pantalla de setup). Genera `art4hotel.db` y `secret.key` automáticamente.
---
## 2. Despliegue en servidor
### a) Copiar archivos
```bash
scp server.py index.html backup.py usuario@servidor:/ruta/art4hotel-hub/
```
> **No copiar** `secret.key` ni `art4hotel.db` desde tu máquina local — el servidor genera los suyos en el primer arranque (o migra los existentes en producción). Los datos del negocio viven solo en el servidor.
### b) Servicio systemd
`/etc/systemd/system/art4hotel-hub.service`:
```ini
[Unit]
Description=Art4Hotel Hub
After=network.target
[Service]
Type=simple
User=claude
WorkingDirectory=/ruta/art4hotel-hub
ExecStart=/usr/bin/python3 server.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now art4hotel-hub
sudo systemctl status art4hotel-hub
```
### c) Redespliegue (actualizar código)
```bash
scp server.py index.html usuario@servidor:/ruta/art4hotel-hub/
ssh usuario@servidor 'sudo systemctl restart art4hotel-hub'
```
Las migraciones de esquema corren solas al reiniciar (idempotentes).
---
## 3. Acceso remoto (Tailscale)
El Hub vive en una red privada **Tailscale** (MagicDNS `iclaude`):
- **Privado** (equipos en la tailnet): `http://iclaude:4401/`
- **Público** (si se requiere exponer): Tailscale **Funnel** en un puerto dedicado (443/8443/10000).
```bash
tailscale funnel --bg 4401 # expone el Hub públicamente
tailscale funnel status # ver qué está expuesto
```
> Nota de seguridad: el Hub ya tiene login propio, pero exponerlo públicamente amplía la superficie de ataque. Mantener el login activo y contraseñas fuertes.
---
## 4. Respaldos
`backup.py` por cron diario:
```cron
0 3 * * * cd /ruta/art4hotel-hub && /usr/bin/python3 backup.py
```
Hace: online backup de la DB (seguro con WAL) + `uploads.tar.gz`. Retiene 30 días en `backups/`.
También disponible bajo demanda desde la UI (`POST /api/backup-now`).
---
## 5. Reloj del servidor (importante)
El servidor de producción tiene CMOS muerto y NTP bloqueado; un reloj desfasado **rompe los logins basados en token** (Filebrowser/Immich) y firma de sesiones.
Mitigación: cron `sync_clock.sh` que toma el header `Date` de `1.1.1.1` y ajusta la hora. Verificar `date` tras reinicios.
---
## 6. Solución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
| "Sesión inválida" constante | reloj del servidor desfasado | corregir hora; revisar `sync_clock.sh` |
| `secret.key` regenerado → todos deslogueados | se borró/cambió la clave | restaurar `secret.key`; es la clave de firma |
| Imágenes equivocadas como ejemplo | prefijos de archivo | ver lógica `first_image` en `/api/file-counts` |
| No resuelve nombres públicos | DNS de Tailscale sin upstream | `tailscale set --accept-dns=false` |
---
*Stack deliberadamente mínimo: todo el sistema cabe en una USB y corre con un solo `python3 server.py`.*

90
MODELO_DATOS.md Normal file
View File

@@ -0,0 +1,90 @@
# Modelo de Datos — Art4Hotel Hub
Base de datos **SQLite** (`art4hotel.db`), modo WAL + foreign keys ON. 14 tablas.
Todas las tablas tienen `id INTEGER PRIMARY KEY AUTOINCREMENT` y, la mayoría, `created_at` / `updated_at`.
> El esquema se crea/migra automáticamente al arrancar (`init_db()` en `server.py`), con migraciones idempotentes (`ALTER TABLE` solo si la columna no existe).
---
## Diagrama de relaciones (lógicas)
```
propuestas ──(propuesta_id)──> oc ──(oc_id)──> ordenes
└──(proyecto_id)──> proyectos ──> productos
clientes ───────(por nombre)───────> oc, ordenes, proyectos
productos <──(por nombre)── ordenes (los "ejemplos" del producto = pedidos con foto)
trabajos ──> tipos de personalización (CSV en productos.tipos_trabajo_disponibles)
```
Las relaciones son por **FK nullable** (`oc.propuesta_id`, `ordenes.oc_id`, `ordenes.proyecto_id`) o **por nombre** (cliente/producto como texto). El CRUD es genérico vía el dict `TABLES` en `server.py`.
---
## Tablas
### `ordenes` — Pedidos (líneas de producción) · 36 columnas
El corazón operativo. Cada fila = una pieza/lote en producción.
| Campo | Notas |
|---|---|
| `orden_id` | ID legible (ej. `ORD-2026-071`). Se genera con MAX+1 |
| `tipo_orden` | OC / Resurtido / Muestra / Defecto / Faltante |
| `cliente`, `producto`, `sku`, `cantidad` | datos del pedido |
| `tipo_trabajo` | personalización (Bordado, Serigrafía, DTF UV…) |
| `stage` | etapa del kanban |
| `fecha_oc/inicio/estimada/recepcion/entrega`, `recibio` | fechas y firma |
| `urgente`, `logo_instrucciones`, `notas` | |
| `costo_producto`, `costo_trabajo`, `costo_logistica`, `precio_factura` | economía |
| `check_facturada/empacada/etiquetas/vehiculo` | checklist |
| `grupo_oc`, `piezas_recibidas`, `piezas_danadas`, `nota_recepcion` | recepción/parciales |
| `oc_id` → oc, `proyecto_id` → proyectos | vínculos |
| `web_ejemplo`, `web_etiqueta` | curaduría web: si la foto de este pedido es ejemplo público + etiqueta pública (zona, NO el cliente real) |
### `oc` — Órdenes de Compra · 22 columnas
Agrupan pedidos de un cliente; llevan la facturación/cobranza.
`oc_id`, `cliente`, `fecha_oc`, `fecha_entrega`, `recibio`, `costo_logistica`, `precio_factura`, `factura_num`, `condiciones_pago`, `status`, `iva_pct`, `otros_gastos(+desc)`, `pagado`, `fecha_pago`, `metodo_pago`, `oc_origen_id` (entregas parciales), `propuesta_id` (de qué propuesta nació).
### `productos` — Catálogo (fuente única de verdad) · 18 columnas
`sku`, `nombre`, `categoria`, `talla`, `material`, `medidas`, `descripcion_web`, `tipos_trabajo_disponibles` (CSV de personalizaciones que aplican), `costo_base`, `proveedor`, `stock_actual`, `punto_reorden`, `activo`, `mostrar_en_web` (publicar en art4hotel.com).
### `proyectos` — Recetas recurrentes · 17 columnas
Receta autorizada reutilizable. `nombre`, `producto_id/_nombre`, `cliente`, `tipo_trabajo`, `costo_unitario`, `costo_trabajo`, `logo_descripcion`, `logo_archivo`, `foto_terminado`, `activo`, `veces_usado`, `ultimo_uso`.
### `propuestas` — Cotizaciones · 19 columnas
`numero`, `cliente_nombre`, `contacto`, `empresa`, `direccion`, `locacion`, `tipo_negocio`, `email`, `telefono`, `fecha`, `vigencia_dias`, `items` (JSON), `iva_pct`, `descuento_pct`, `status` (borrador/enviada/aceptada/rechazada).
### `catalogos` — Presentaciones PDF · 17 columnas
`nombre`, `segmento`, `cliente_nombre`, `fecha`, `items` (JSON con snapshot + precio manual), `show_prices/clientes/contacto`, `entrega`, `minimo_compra`, `terminos`, `status`.
### `clientes` — CRM · 10 columnas
`nombre`, `tipo` (hotel/restaurante/tienda…), `contacto`, `zona_entrega`, `costo_entrega`, `condiciones_pago`, `notas`, `activo`.
### `trabajos` — Tipos de personalización · 8 columnas
`clave`, `nombre`, `costo_base`, `variable_por`, `proveedor_default`, `activo`. Fuente de la lista de personalizaciones.
### `inventario` — SKUs legacy · 15 columnas
`sku`, `nombre`, `descripcion`, `tipo`, `talla`, `color_base`, `proveedor`, `stock_inicial`, `punto_reorden`, `costo_unitario`, `ultimo_conteo`.
### `compras` — Compras a proveedor · 13 columnas
`compra_id`, `proveedor`, `fecha_compra`, `fecha_llegada`, `sku`, `nombre_producto`, `cantidad`, `costo_unitario`, `status`.
### `modelos` · `materiales` — Catálogos auxiliares
`modelos`: clave, nombre, descripcion, activo. `materiales`: clave, nombre, tipo, costo_unitario, activo.
### `tareas` — Kanban interno del equipo · 11 columnas
`titulo`, `descripcion`, `prioridad`, `stage`, `categoria`, `fecha_limite`, `asignado`, `notas`.
### `bitacora` — Timeline de eventos · 6 columnas
`tipo`, `titulo`, `descripcion`, `referencia`, `fecha`. Registro de acciones del sistema.
### `usuarios` — Login del Hub · 6 columnas
`username`, `pass_hash` (PBKDF2-SHA256, 200k iteraciones, con salt), `nombre`, `activo`. La sesión usa cookie firmada con HMAC (clave en `secret.key`, NO versionada).
---
## Archivos (no en DB)
Guardados en `uploads/{entidad}/` con prefijo de tipo en el nombre:
`factura_`, `recibo_entrega_`, `foto_producto_base_`, `foto_avance_produccion_`, `comprobante_`, etc.
**El prefijo es crítico**: `/api/file-counts` lo usa para elegir la imagen representativa, **prefiriendo** fotos (`foto_`/`mockup`) y **excluyendo** documentos (`factura`, `recibo`, `comprobante`, `soporte`, `contrato`, `propuesta`).

147
NEGOCIO.md Normal file
View File

@@ -0,0 +1,147 @@
# Art4Hotel / Clod — Contexto de negocio
> Doc de contexto operativo. Léelo antes de proponer cambios al Hub.
> Última actualización: 2026-05-28
## 1. Qué es el negocio
Empresa de **bolsas y accesorios personalizados** con base en Los Cabos. Cliente principal: hoteles boutique y tiendas de la región. Marca paraguas: *Art4Hotel*.
**Modelo**: comercial + logística. No hay fábrica propia. Todo lo que se vende es:
1. Producto base de un proveedor (bolsa, taza, accesorio)
2. + Personalización hecha por un taller externo (bordado, serigrafía, DTF UV, etc.)
Clod (operador principal) hace **todo el ciclo end-to-end excepto fabricar**:
- Cierra venta y manda propuesta
- Recibe mercancía base del proveedor
- Lleva físicamente en su carro al taller de personalización
- Recoge el producto terminado
- Empaca
- Entrega físicamente con el cliente
**Cobranza**: solo de los clientes que él mismo cerró. Sandra y otros vendedores cobran los suyos. Las ventas están divididas por cliente entre el equipo.
## 2. Mezcla del negocio
- **80% hoteles** (Los Cabos)
- **20% otros** (tiendas, eventos, distribuidores)
## 3. Clientes recurrentes
### Top actuales
- **Nobu Los Cabos** — hotel luxury, volúmenes consistentes
- **Flora Farms / Flora Mango** — restaurante destino, logo recurrente
- **Tienda La Ventana** — tienda recurrente que resurte
- **Tienda El Sargento** — tienda recurrente que resurte
### Otros relevantes
Cabo Bello, Hacienda Cabo Bello, Concepción, Mas Olas Todos Santos, Puerta Cortés, Puerto Los Cabos, Tres Guerras, The Cape, Wild Cabo, Café Saturno, Todos Santos Hotel Boutique, Hnos Salgados, El Burro Terko, Chapitos Los Barriles, Tienda Los Torotes, Tienda Centro, Rubas, Ventanas, Corazón.
### Reglas por tipo de cliente
- **Hoteles** → factura formal siempre. Confianza alta (no requieren anticipo para arrancar producción).
- **Tiendas** → mezcla: algunas factura, algunas consignación.
- **Trigger de producción**: la **propuesta aceptada basta** para mandar a producción. No se espera anticipo.
## 4. Productos
No hay un producto dominante — **mezcla balanceada**. Los más recurrentes:
- Bolsa de manta con base de yute (versátil, logo donde sea)
- Bolsa Loneta 2026 (asas café o negro)
- Bolsa Cabo Bello
- Maleta de viaje de manta gruesa
- Bolsa yute grande asas café
- Taza blanca minimalista
- Sombrero (con/sin piel)
- Mandil de mezclilla
- Llaveros
- Termo doble pared
## 5. Personalizaciones (tipos de trabajo)
Las más pedidas / rentables:
- **Serigrafía** — volumen alto, costo bajo, márgenes amplios en pedidos grandes
- **Bordado** — premium, hoteles lo piden para imagen, márgenes buenos pero más caro
- **DTF UV / Impresión DTF** — para tazas, sustratos rígidos, diseños complejos a todo color
Lista canónica de personalizaciones (sincronizada con tabla `trabajos`):
`Bordado, Serigrafia, Grabado laser, Impresion DTF, Sublimación, DTF UV, Costura, Modificacion, Sin personalización, Otro`.
Si se agrega una nueva, registrarla en el catálogo (`Catalogo → Trabajos`) con su costo base. El Hub la verá en pedido / propuesta / proyecto automáticamente.
## 6. Talleres / proveedores de personalización
- **2 Mares** — taller externo, el principal. Bordado / serigrafía estándar.
- **Taller Sofía** — taller externo de costura.
- Cobran su trabajo por pieza.
- **Logística**: Clod los recorre en su carro — llevar mercancía base, recoger terminado.
El stage **"En 2 Mares"** y **"En Taller Sofía"** en el kanban representan literalmente "el producto está físicamente ahí".
## 7. Flujo de un pedido (stages)
```
Nuevo
↓ (Clod lleva en carro al taller)
En 2 Mares / En Taller Sofía
↓ (Clod va y recoge — botón "Recoger")
En Almacen
↓ (Clod carga el carro para entregar)
En Vehiculo
↓ (entrega física con firma)
Entregado
```
Stages adicionales:
- **En Tránsito** — producto base viajando del proveedor (poco usado, opcional)
- **Cancelado** — pedido abortado
## 8. Tipos de pedido (`tipo_orden`)
| Tipo | Significado |
|---|---|
| `OC` | Pedido principal de cliente, con orden de compra y factura |
| `Muestra` | 1 pieza para presentar a un prospecto antes de cerrar venta |
| `Resurtido` | Producción libre sin OC, va a bodega como stock disponible (POS / venta rápida) |
| `Defecto` | Reposición cuando llegó producto dañado |
| `Faltante` | Reposición cuando faltaron piezas en una entrega |
**Regla**: todo pedido `OC` vive vinculado a una `oc`. Resurtido/Muestra/Defecto/Faltante pueden vivir sueltos.
## 9. Estructura conceptual
- **Pedido** (`ordenes`) — una línea de producción (1 producto, 1 personalización, 1 cantidad)
- **Orden de Compra (OC)** (`oc`) — agrupa varios pedidos del mismo cliente, lleva la factura/cobranza
- **Proyecto recurrente** (`proyectos`) — receta autorizada (cliente + producto + tipo_trabajo + logo). Se reusa entre pedidos para no volver a definir el diseño.
- **Propuesta** (`propuestas`) — cotización antes de cerrar venta. Al aceptarse, se convierte en OC + pedidos.
## 10. Operación diaria
**No hay rutina fija** — depende de carga semanal. Lo que más urge ver al abrir el Hub:
> **Qué hay listo en talleres para recoger** — para planear los viajes del día.
Por eso vale la pena tener visibles:
- Pedidos `En 2 Mares` / `En Taller Sofía` con tiempo acumulado
- Pedidos `En Vehículo` listos para entregar (panel ya implementado en Entregas)
## 11. Equipo
| Persona | Rol |
|---|---|
| **Clod** | Fundador. Operaciones end-to-end (logística, talleres, entregas) + cierra sus propias ventas |
| **Tess** | Socia. Diseño. |
| **Andre** | Repartidor / entregas físicas. |
| **Sandra** | Contabilidad, facturación. Cierra sus propios clientes y cobra. |
Las ventas están **divididas por cliente** entre vendedores. Cada quien cobra lo suyo.
## 12. Por documentar (pendiente)
- Cobranza: ciclos típicos, condiciones de pago por cliente, anticipos
- Propuestas: estructura típica, cómo se arma una, qué incluye
- Decisiones de diseño técnico del Hub (por qué cada feature está como está)
- Plan a futuro: integraciones, automatizaciones deseadas

63
ONBOARDING.md Normal file
View File

@@ -0,0 +1,63 @@
# Art4Hotel Hub — Handover
## Que es
Sistema interno de gestion de ordenes, inventario, entregas y ventas para Art4Hotel (bolsas personalizadas para hoteles en Los Cabos). Single-page app con Python stdlib server + SQLite.
## Acceso servidor
- **Host**: `claude@192.168.50.46`
- **Path**: `/mnt/iclaude/art4hotel-hub/`
- **Archivos**: `server.py` (backend), `index.html` (frontend SPA), `art4hotel.db` (SQLite)
- **Servicio**: `sudo systemctl restart art4hotel-hub`
- **Puerto**: 4401
- **Deploy**: `scp archivo claude@192.168.50.46:/mnt/iclaude/art4hotel-hub/ && ssh claude@192.168.50.46 "sudo systemctl restart art4hotel-hub"`
## Stack
- Python 3 stdlib (`http.server` + `sqlite3` + `json`) — zero deps
- SQLite WAL mode, foreign keys
- Frontend vanilla JS SPA, no frameworks
- Branding: Outfit font, paleta olive/sand/cream del brandbook Art4Hotel
## Arquitectura
**Tablas principales**: ordenes, inventario, tareas, bitacora, modelos, materiales, trabajos, clientes, productos, archivos
**API REST**: `/api/{tabla}` GET/POST, `/api/{tabla}/{id}` GET/PUT/DELETE
**Archivos**: `/api/upload/{orden_id}?tipo=X` POST multipart, `/api/files/{orden_id}` GET
## Flujo de ordenes (stages kanban)
`En 2 Mares``En Taller Sofia``En Almacen``En Vehiculo``Entregado`
- Drag & drop entre columnas
- Al mover a "Entregado" se abre modal de confirmacion (fecha, recibio, upload soporte)
- Boton "Entregar" directo en tarjetas de "En Vehiculo"
## Tabs del SPA
1. **Dashboard** — KPIs, stages bar chart, clientes activos, alertas stock, timeline
2. **Ventas** — Revenue por cliente, por mes, margenes, entregas del mes
3. **Ordenes** — Kanban + tabla, wizard 4-pasos para crear, search, toggle entregados
4. **Entregas** — Post-entrega agrupado por fecha; editar costos inline, subir facturas/recibos/fotos
5. **Inventario** — SKUs con stock, reorden, costos
6. **Catalogo** — CRUD: modelos, materiales, trabajos, clientes (fuente de datos para dropdowns)
7. **Tareas** — Kanban interno del equipo
8. **Bitacora** — Timeline de eventos/decisiones
## Modelo de precios
```
costo_total = (costo_producto + costo_trabajo) * cantidad + logistica
utilidad = precio_factura - costo_total
margen = utilidad / precio_factura * 100
```
## Equipo
- **Clod** — fundador, operaciones, entregas
- **Tess** — socia, diseño
- **Andre** — repartidor, entregas fisicas
- **Sandra** — contabilidad, facturacion
## Archivos locales (dev)
- `C:\Users\claud\Documents\Claude\Art 4 Hotel\hub\` — server.py + index.html
- `C:\Users\claud\Documents\Claude\Art 4 Hotel\Pagina Web\art4hotel\Recursos\art4hotel-brandbook.html` — referencia de marca
## Notas rapidas
- El usuario actualiza datos reales de inventario/modelos el mismo (CRUD en Catalogo)
- Mobile-first para Andre/Sandra (wizard, botones grandes)
- Entregas agrupadas por fecha para navegacion cronologica
- Archivos se guardan en `/mnt/iclaude/art4hotel-hub/uploads/{orden_id}/`

111
README.md Normal file
View File

@@ -0,0 +1,111 @@
# Art4Hotel Hub
Sistema interno de gestión integral (ERP a la medida) para **Art4Hotel** — bolsas y accesorios personalizados para hoteles, restaurantes y eventos en Los Cabos, México.
Centraliza todo el ciclo del negocio: **cotización → producción → bodega → entrega → cobranza**, más un catálogo digital, generador de catálogos PDF y sincronización con el sitio web público.
---
## ✨ Características
- **Operaciones** — kanban de producción con drag & drop por etapas
- **Ventas** — dashboard de KPIs, workflow de cobranza por OC, gestión de entregas
- **Clientes** — CRM con historial, condiciones de pago, zonas
- **Propuestas** — cotizador visual; convierte propuestas aceptadas en órdenes + pedidos
- **Catálogos** — generador de catálogo PDF con la marca, con storytelling (producto base + galería de personalizaciones)
- **Productos** — catálogo unificado (fuente única de verdad)
- **Inventario** — SKUs, stock, punto de reorden
- **Archivos** — facturas, fotos, recibos con nombres inteligentes
- **Sitio web** — alimenta art4hotel.com vía script de sincronización
- **Login** — autenticación con sesiones firmadas (HMAC)
- **Respaldos** — automáticos diarios
---
## 🧱 Stack
| Capa | Tecnología |
|---|---|
| Backend | **Python 3 stdlib** (`http.server` + `sqlite3`) — cero dependencias externas |
| Base de datos | **SQLite** (WAL, foreign keys) — un solo archivo |
| Frontend | **Vanilla JS SPA** en un solo `index.html` — sin frameworks |
| Archivos | Filesystem (`uploads/`) servido por el mismo server |
| Deploy | `scp` + `systemctl` — sin Docker, sin CI/CD |
Filosofía: **minimalismo deliberado**. Todo el sistema (código + datos) pesa <1 MB. Cabe en una USB. Sin licencias, sin dependencias que mantener.
---
## 🚀 Arranque rápido
```bash
# Requiere solo Python 3 (stdlib). Sin pip install.
python3 server.py
# → http://localhost:4401
```
La primera vez, la app muestra una pantalla para **crear la cuenta de administrador**. Después, login normal.
Ver **[INSTALACION.md](./INSTALACION.md)** para el despliegue completo en servidor.
---
## 📁 Estructura del repositorio
```
art4hotel-hub/
├── server.py # Backend: HTTP server + SQLite + API REST + auth
├── index.html # Frontend: SPA completa (~9000 líneas)
├── backup.py # Script de respaldo (DB + uploads)
├── fix_dup.py # Utilidad: corrige IDs duplicados
├── README.md # Este archivo
├── INSTALACION.md # Guía de despliegue desde cero
├── MODELO_DATOS.md # Esquema de la base de datos (14 tablas)
├── API.md # Referencia de endpoints
├── CONTEXTO.md # Estado técnico: decisiones, optimizaciones, deploy
├── NEGOCIO.md # Contexto operativo: clientes, productos, flujo real
├── WEB.md # Sitio público art4hotel.com + sync
├── ONBOARDING.md # Handover técnico rápido
├── CLAUDE.md # Índice maestro + contexto para asistentes IA
└── VALOR_SISTEMA.md # Valor/costo del sistema (para presentar a terceros)
```
> **No incluidos en el repo** (por seguridad / ver `.gitignore`): `art4hotel.db` (datos del negocio), `secret.key` (clave de sesiones), `ACCESOS.html` (contraseñas), `uploads/`, `backups/`.
---
## 📚 Documentación
| Documento | Para qué |
|---|---|
| **[NEGOCIO.md](./NEGOCIO.md)** | Entender el negocio: clientes, productos, flujo real, reglas. **Léelo primero.** |
| **[CONTEXTO.md](./CONTEXTO.md)** | Estado técnico: modelo de datos, producto unificado, catálogos, decisiones UX |
| **[MODELO_DATOS.md](./MODELO_DATOS.md)** | Esquema completo de la base de datos |
| **[API.md](./API.md)** | Referencia de endpoints REST |
| **[INSTALACION.md](./INSTALACION.md)** | Desplegar desde cero |
| **[WEB.md](./WEB.md)** | Sitio público + sincronización |
| **[ONBOARDING.md](./ONBOARDING.md)** | Repaso técnico rápido |
| **[VALOR_SISTEMA.md](./VALOR_SISTEMA.md)** | Valor/costo del sistema |
---
## 🔑 Conceptos clave
- **Pedido** (`ordenes`) — línea de producción individual (1 producto × 1 personalización × cantidad)
- **Orden de Compra (OC)** (`oc`) — agrupa pedidos, lleva la factura/cobranza
- **Proyecto recurrente** (`proyectos`) — receta autorizada reutilizable (cliente + producto + trabajo + logo)
- **Propuesta** (`propuestas`) — cotización; al aceptarse genera OC + pedidos
- **Stages** del kanban: `Nuevo → En 2 Mares / En Taller Sofía → En Almacén → En Vehículo → Entregado`
### ⚠️ Principio de diseño: el producto es la fuente única de verdad
Cada atributo de producto impacta hasta 3 funciones. Antes de agregar/cambiar uno, definir su impacto en: **(1) Operación/producción · (2) Catálogo/cotizador · (3) Página web**.
---
## 🛡 Respaldos
`backup.py` corre por cron diario: snapshot de la DB (online backup, seguro con WAL) + `uploads.tar.gz`. Retiene 30 días.
---
*Sistema desarrollado para Art4Hotel · Los Cabos, BCS, México.*

138
VALOR_SISTEMA.html Normal file
View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Art4Hotel — Valor del Sistema</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Outfit:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
<style>
:root{--olive:#5C6B4F;--olive-dark:#3D4A33;--brown:#6B4F3C;--sand:#D4C5A9;--sand-light:#E8DFC8;--sand-pale:#F5F0E5;--cream:#FAF7F0;--charcoal:#2C2C2C;--gray:#8A8075}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Outfit',sans-serif;color:var(--charcoal);background:var(--sand-pale);line-height:1.6;font-size:15px;padding:30px 16px}
.page{max-width:860px;margin:0 auto;background:#fff;border-radius:14px;box-shadow:0 10px 40px rgba(0,0,0,.08);overflow:hidden}
.cover{background:linear-gradient(135deg,var(--olive-dark),var(--olive));color:#fff;padding:54px 50px;position:relative;overflow:hidden}
.cover::after{content:'';position:absolute;top:-30%;right:-10%;width:400px;height:400px;background:radial-gradient(circle,rgba(212,197,169,.18),transparent 65%);border-radius:50%}
.cover .logo{font-family:'Playfair Display',serif;position:relative;z-index:1}
.cover .logo .a{font-family:'Outfit';font-weight:600;font-size:34px;letter-spacing:2px}
.cover .logo .four{font-style:italic;font-size:38px;color:var(--sand)}
.cover h1{font-family:'Playfair Display',serif;font-style:italic;font-size:36px;font-weight:400;margin-top:24px;position:relative;z-index:1;line-height:1.15}
.cover .sub{font-size:13px;color:var(--sand-light);margin-top:12px;position:relative;z-index:1}
.body{padding:40px 50px}
h2{font-family:'Playfair Display',serif;font-style:italic;font-size:26px;color:var(--olive-dark);font-weight:400;margin:38px 0 14px;padding-bottom:8px;border-bottom:2px solid var(--sand)}
h2:first-child{margin-top:0}
h3{font-size:16px;color:var(--olive-dark);margin:22px 0 8px;font-weight:600}
p{margin:10px 0}
ul,ol{margin:10px 0 10px 22px}
li{margin:5px 0}
strong{color:var(--olive-dark)}
.diagram{background:var(--charcoal);color:#e8e8e8;font-family:'DM Sans',monospace;font-size:11px;line-height:1.5;padding:20px;border-radius:10px;overflow-x:auto;white-space:pre;margin:16px 0}
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:14px}
th{background:var(--olive);color:#fff;text-align:left;padding:10px 12px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
td{padding:9px 12px;border-bottom:1px solid var(--sand-light)}
tr:nth-child(even) td{background:var(--cream)}
.callout{background:var(--sand-pale);border-left:4px solid var(--olive);border-radius:0 8px 8px 0;padding:14px 18px;margin:16px 0;font-size:14px}
.callout.big{background:linear-gradient(135deg,var(--cream),var(--sand-light));border-color:var(--brown)}
.callout .big-num{font-family:'Playfair Display',serif;font-style:italic;font-size:24px;color:var(--olive-dark);display:block;margin-top:4px}
.foot{text-align:center;color:var(--gray);font-size:11px;padding:24px;border-top:1px solid var(--sand-light)}
.print-tip{max-width:860px;margin:0 auto 16px;background:#fff3cd;color:#856404;padding:10px 16px;border-radius:8px;font-size:13px;text-align:center}
@media print{body{background:#fff;padding:0}.page{box-shadow:none;border-radius:0;max-width:none}.print-tip{display:none}}
</style>
</head>
<body>
<div class="print-tip">💡 Para guardar como PDF: Ctrl+P → Destino "Guardar como PDF". (Este aviso no aparece en el PDF)</div>
<div class="page">
<div class="cover">
<div class="logo"><span class="a">art</span> <span class="four">4</span> <span class="a">hotel</span></div>
<h1>Estructura, Hosting y Valor del Sistema</h1>
<div class="sub">Documento para explicar el sistema a socios, inversionistas o proveedores · 2026</div>
</div>
<div class="body">
<h2>1 · Qué es</h2>
<p>Un <strong>ERP a la medida</strong> (sistema de gestión integral) para Art4Hotel. Centraliza en una sola plataforma todo el ciclo del negocio: cotización → producción → bodega → entrega → cobranza, más un catálogo digital y un sitio web público que captura clientes.</p>
<p>Hecho específicamente para el flujo de bolsas/accesorios personalizados — no es un software genérico adaptado, sino uno que "entiende" qué es una bolsa con bordado para un hotel.</p>
<h2>2 · Arquitectura técnica</h2>
<div class="diagram">USUARIOS (Clod, Sandra, Andre, Tess)
Navegador / Celular — acceso privado por VPN
▼ Internet privado (Tailscale VPN)
SERVIDOR (1 máquina pequeña)
┌────────────┐ ┌──────────────┐ ┌──────────────────┐
│ App Python │→ │ Base SQLite │ │ Archivos/fotos │
│ (servidor) │ │ (1 archivo) │ │ (facturas, fotos)│
└────────────┘ └──────────────┘ └──────────────────┘
│ respaldo automático diario (30 días)
▼ sincronización (script)
SITIO WEB PÚBLICO — art4hotel.com (gratis, GitHub)
Catálogo + formulario de cotización → llega al correo</div>
<p><strong>Claves:</strong> stack minimalista (Python + SQLite + JavaScript), <strong>cero dependencias, cero licencias</strong>. Todo el código pesa &lt;1 MB. Privado por VPN; la web pública es una capa separada de solo lectura. Respaldos diarios automáticos.</p>
<h2>3 · Dónde vive — hosting y costo</h2>
<h3>Opción A — Servidor propio (lo de hoy) ✅ inversión única</h3>
<table>
<tr><th>Equipo</th><th>Costo (MXN)</th><th>Notas</th></tr>
<tr><td>Mini PC (Intel N100, 16GB, SSD 500GB)</td><td>$3,500 $5,000</td><td>Recomendado, silencioso</td></tr>
<tr><td>Raspberry Pi 5 (8GB) + kit</td><td>~$3,000</td><td>Alternativa económica</td></tr>
<tr><td>Reusar una PC vieja</td><td>$0</td><td>Funciona perfecto</td></tr>
<tr><td>No-break / UPS</td><td>$800 $1,500</td><td>Protege ante apagones</td></tr>
<tr><td><strong>Total inversión única</strong></td><td><strong>~$4,000 $6,500</strong></td><td>+ ~$50/mes luz</td></tr>
</table>
<h3>Opción B — Nube (VPS rentado) · renta mensual</h3>
<table>
<tr><th>Proveedor</th><th>Plan</th><th>Costo/mes (MXN)</th></tr>
<tr><td>Hetzner</td><td>2 CPU / 4GB</td><td>~$90</td></tr>
<tr><td>DigitalOcean</td><td>Básico</td><td>~$110</td></tr>
<tr><td>Vultr / Linode</td><td>Similar</td><td>$100 $150</td></tr>
</table>
<p>Sitio web público art4hotel.com: <strong>gratis</strong> (GitHub Pages, SSL incluido). Solo el dominio ~$200/año.</p>
<div class="callout">Hoy operamos con <strong>servidor propio (~$5,000 una vez)</strong>. Migrar a nube costaría ~$100150/mes. La web es gratis.</div>
<h2>4 · Qué incluye el sistema</h2>
<div class="diagram">ART4HOTEL HUB
├── 🛠 OPERACIONES — kanban de producción
├── 💼 VENTAS — Dashboard (KPIs, comparativos) · Por OC · Por Entregas
├── 👥 CLIENTES — CRM con historial y condiciones
├── 📝 PROPUESTAS — cotizador → convierte a Orden + Pedidos
├── 📚 CATÁLOGOS — generador PDF con marca + storytelling
├── 📦 PRODUCTOS — catálogo unificado (fuente única)
├── 📁 ARCHIVOS — facturas/fotos con nombres inteligentes
├── 🌐 SITIO WEB — landing + catálogo + captura de leads
└── ❓ MANUAL integrado · 🛡 Respaldos automáticos</div>
<p><strong>Funciones que normalmente cuestan extra en software comercial:</strong> generador de catálogo PDF con tu marca, dashboard de analítica con comparativos, sitio web con captura de leads, conversión automática cotización→producción, curaduría de qué se publica (protegiendo tu cartera de clientes).</p>
<h2>5 · Cuánto costaría hacerlo desde cero</h2>
<table>
<tr><th>Fase</th><th>Tiempo</th></tr>
<tr><td>Levantamiento y diseño</td><td>1 2 semanas</td></tr>
<tr><td>Backend + base de datos + API</td><td>2 3 semanas</td></tr>
<tr><td>Frontend (la app, ~9,000 líneas)</td><td>6 10 semanas</td></tr>
<tr><td>Catálogo PDF + sitio web + sync</td><td>3 4 semanas</td></tr>
<tr><td>Pruebas, despliegue, manual</td><td>1 2 semanas</td></tr>
<tr><td><strong>Total (~400600 horas)</strong></td><td><strong>~3.5 5 meses</strong></td></tr>
</table>
<table>
<tr><th>Perfil</th><th>Tarifa</th><th>Costo del proyecto</th></tr>
<tr><td>Freelancer mid (México)</td><td>$350550 MXN/hr</td><td>$150,000 $330,000 MXN</td></tr>
<tr><td>Freelancer senior (México)</td><td>$6001,000 MXN/hr</td><td>$250,000 $500,000 MXN</td></tr>
<tr><td>Agencia de software (México)</td><td>proyecto fijo</td><td>$250,000 $600,000 MXN</td></tr>
<tr><td>Internacional (USD)</td><td>$3070 USD/hr</td><td>$12,000 $35,000 USD</td></tr>
</table>
<div class="callout big">Rango realista para construirlo profesionalmente desde cero:
<span class="big-num">~$180,000 a $450,000 MXN (≈ $11,000 $27,000 USD) · 3.5 5 meses</span>
</div>
<p><strong>Mantenimiento</strong> si lo hiciera un tercero: $5,000 $15,000 MXN/mes, o ~1520% del costo al año.</p>
<p><strong>Vs. software de renta (SaaS):</strong> uno comparable costaría $3,000 $12,000 MXN/mes y aun así no se adaptaría al flujo de personalización (talleres externos, bodega con/sin orden, catálogo con storytelling).</p>
<h2>6 · Conclusión de valor</h2>
<ul>
<li><strong>Lo que tienes:</strong> un sistema a la medida valuado en <strong>$180k450k MXN</strong> de desarrollo, operando con <strong>~$5,000 de equipo</strong> y <strong>$0 de licencias</strong>.</li>
<li><strong>Sin costo recurrente de software</strong> (vs $36k144k/año de un SaaS).</li>
<li><strong>Datos en tu poder</strong>, adaptado exactamente a cómo trabaja Art4Hotel.</li>
<li><strong>Escalable:</strong> para más usuarios/volumen, migrar a Postgres y nube es trabajo de días, no meses.</li>
</ul>
</div>
<div class="foot">Documento generado 2026 · Las cifras de mercado son estimaciones y varían por proveedor y región.</div>
</div>
</body>
</html>

176
VALOR_SISTEMA.md Normal file
View File

@@ -0,0 +1,176 @@
# Art4Hotel Hub — Estructura, Hosting y Valor del Sistema
> Documento para explicar el sistema a socios, inversionistas o proveedores.
> Cifras en MXN con referencia USD (tipo de cambio ~$17.5). Estimaciones 2026.
---
## 1. Qué es
Un **ERP a la medida** (sistema de gestión integral) para Art4Hotel. Centraliza en una sola plataforma todo el ciclo del negocio: cotización → producción → bodega → entrega → cobranza, más un catálogo digital y un sitio web público que captura clientes.
Hecho específicamente para el flujo de bolsas/accesorios personalizados — no es un software genérico adaptado, sino uno que "entiende" qué es una bolsa con bordado para un hotel.
---
## 2. Arquitectura técnica
```
┌──────────────────────────────────────────────────────────┐
│ USUARIOS (Clod, Sandra, Andre, Tess) │
│ Navegador / Celular — acceso privado por VPN │
└───────────────────────┬──────────────────────────────────┘
│ Internet privado (Tailscale VPN)
┌──────────────────────────────────────────────────────────┐
│ SERVIDOR (1 máquina pequeña) │
│ ┌────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ App Python │ │ Base de datos │ │ Archivos/fotos │ │
│ │ (servidor │→ │ SQLite │ │ (facturas, fotos │ │
│ │ web) │ │ (1 archivo) │ │ de producto) │ │
│ └────────────┘ └──────────────┘ └──────────────────┘ │
│ ↓ respaldo automático diario (30 días) │
└───────────────────────┬──────────────────────────────────┘
│ script de sincronización (1 vez/cambio)
┌──────────────────────────────────────────────────────────┐
│ SITIO WEB PÚBLICO — art4hotel.com (gratis, GitHub) │
│ Catálogo + formulario de cotización → llega al correo │
└──────────────────────────────────────────────────────────┘
```
**Características técnicas clave:**
- Stack minimalista: Python + SQLite + JavaScript. **Cero dependencias externas**, cero licencias.
- Todo el sistema (programa + datos) pesa menos de 1 MB de código — cabe en una USB.
- Privado: solo accesible por VPN, no expuesto a internet. La web pública es una capa separada de solo lectura.
- Respaldos automáticos diarios.
---
## 3. Dónde vive el sistema — opciones de hosting y costo
### Opción A — Servidor propio (lo que usamos hoy) ✅
Una máquina pequeña en la oficina/casa. **Inversión única, sin renta mensual.**
| Equipo | Costo aprox. (MXN) | Notas |
|---|---|---|
| Mini PC (Intel N100, 16GB, SSD 500GB) | $3,500 $5,000 | Recomendado. Silencioso, bajo consumo |
| Raspberry Pi 5 (8GB) + kit | ~$3,000 | Alternativa económica |
| Reusar una PC vieja | $0 | Funciona perfecto, el sistema es ligero |
| No-break / UPS (600VA) | $800 $1,500 | Protege ante apagones |
| **Total inversión única** | **~$4,000 $6,500** | + ~$50/mes de electricidad |
**Pros**: pagas una vez, control total, datos en tu poder. **Contras**: depende de tu internet/luz local (mitigado con VPN + UPS).
### Opción B — Nube (servidor rentado / VPS)
Un servidor virtual en la nube. **Renta mensual, cero equipo.**
| Proveedor | Plan | Costo/mes (MXN) |
|---|---|---|
| Hetzner | 2 CPU / 4GB RAM | ~$90 (€4.5) |
| DigitalOcean | Básico | ~$110 ($6 USD) |
| Vultr / Linode | Similar | ~$100 $150 |
| **Total** | | **~$100 $150/mes** (~$1,200 $1,800/año) |
**Pros**: accesible desde cualquier lado sin VPN, sin depender de tu luz/internet, respaldos del proveedor. **Contras**: renta perpetua, datos en servidor externo.
### Opción C — Plataforma administrada (PaaS)
Railway / Render / Fly.io — despliegue sin administrar el servidor. ~$0 $400/mes según uso. Innecesario para este tamaño; un VPS de $100/mes sobra.
### Sitio web público
art4hotel.com vive en **GitHub Pages = gratis** (hosting + SSL incluidos). Solo se paga el dominio (~$200/año, ya se tiene en Wix).
> **Resumen hosting**: hoy operamos con servidor propio (~$5,000 una vez). Migrar a nube costaría ~$100150/mes. La web pública es gratis.
---
## 4. Esquema funcional — qué incluye el sistema
```
ART4HOTEL HUB
├── 🛠 OPERACIONES — kanban de producción
│ Pedidos por etapa · drag&drop · recoger de taller · bodega con/sin orden
├── 💼 VENTAS (3 vistas)
│ 📊 Dashboard: facturación, márgenes, comparativo mes/año, tiempos de ciclo, pricing
│ 📋 Por OC: workflow de cobranza
│ 📦 Por Entregas: panel "en vehículo" + histórico
├── 👥 CLIENTES — CRM con historial, condiciones de pago, zonas
├── 📝 PROPUESTAS — cotizador
│ Editor visual · convertir propuesta aceptada → Orden + Pedidos
├── 📚 CATÁLOGOS — generador de catálogo PDF imprimible
│ Builder de productos · precios manuales · términos comerciales
│ Storytelling: producto base + galería de personalizaciones
├── 📦 PRODUCTOS — catálogo unificado (fuente única de verdad)
│ Identidad · personalizaciones por producto · operación/inventario
├── 📁 ARCHIVOS — facturas, fotos, recibos con nombres inteligentes
├── 🌐 SITIO WEB (art4hotel.com)
│ Landing · catálogo dinámico · wizard de cotización → leads al correo
│ Sincronización automática desde el Hub
└── ❓ MANUAL integrado · 🛡 Respaldos automáticos
```
**Funciones destacadas que normalmente cuestan extra en software comercial:**
- Generador de catálogo PDF con tu marca
- Dashboard de analítica de ventas con comparativos
- Sitio web con captura de leads integrada
- Conversión automática cotización → producción
- Curaduría de qué productos/ejemplos se publican (con protección de cartera de clientes)
---
## 5. Valor de desarrollo — cuánto costaría hacerlo desde cero
Si contrataras a un desarrollador o agencia para construir este sistema completo:
### Tiempo estimado (1 desarrollador senior)
| Fase | Tiempo |
|---|---|
| Levantamiento de requerimientos y diseño | 1 2 semanas |
| Backend + base de datos + API | 2 3 semanas |
| Frontend (la app, ~9,000 líneas) | 6 10 semanas |
| Catálogo PDF + sitio web + sincronización | 3 4 semanas |
| Pruebas, despliegue, documentación, manual | 1 2 semanas |
| **Total** | **~3.5 5 meses** |
Equivale a **~400 600 horas** de desarrollo.
### Costo según quién lo desarrolle
| Perfil | Tarifa | Costo total del proyecto |
|---|---|---|
| Freelancer mid (México) | $350 $550 MXN/hr | **$150,000 $330,000 MXN** |
| Freelancer senior (México) | $600 $1,000 MXN/hr | **$250,000 $500,000 MXN** |
| Agencia de software (México) | proyecto fijo | **$250,000 $600,000 MXN** |
| Internacional (USD) | $30 $70 USD/hr | **$12,000 $35,000 USD** |
> **Rango realista para construirlo profesionalmente desde cero:**
> **~$180,000 a $450,000 MXN** (≈ **$11,000 $27,000 USD**), en **3.5 5 meses**.
### Mantenimiento continuo (si lo hiciera un tercero)
- Retainer mensual de soporte/ajustes: **$5,000 $15,000 MXN/mes**, o
- ~15 20% del costo de construcción al año.
### Comparación con software de renta (SaaS)
Un sistema comercial comparable (Odoo, monday.com + integraciones, o un CRM+ERP a renta) costaría **$3,000 $12,000 MXN/mes** según usuarios y módulos — y aun así **no se adaptaría** al flujo específico de personalización (talleres externos, bodega con/sin orden, catálogo con storytelling).
---
## 6. Conclusión de valor
- **Lo que tienes**: un sistema a la medida valuado en **$180k $450k MXN** de desarrollo, operando con **~$5,000 de equipo** (o ~$100/mes en nube) y **$0 de licencias**.
- **Sin costo recurrente de software** (vs $3k12k/mes de un SaaS = $36k144k/año).
- **Datos en tu poder**, adaptado exactamente a cómo trabaja Art4Hotel.
- **Escalable**: para más usuarios/volumen, migrar a una base de datos más robusta (Postgres) y nube — trabajo estimado de días, no meses.
---
*Documento generado 2026-05-31. Las cifras de mercado son estimaciones y varían por proveedor y región.*

68
WEB.md Normal file
View File

@@ -0,0 +1,68 @@
# Art4Hotel — Sitio web público (art4hotel.com)
Landing pública que se alimenta del Hub. Captura leads y presenta productos.
Última actualización: 2026-05-31
## Infraestructura
- **Repo**: `github.com/Claudeandrefg/art4hotel` (rama `main`)
- **Hosting**: GitHub Pages → `art4hotel.com` (apex) + `www.art4hotel.com`
- **SSL**: Let's Encrypt automático (GitHub Pages). DNS gestionado en **Wix**.
- Apex: 4 A records a GitHub Pages `185.199.108-111.153`
- `www` CNAME → `claudeandrefg.github.io`
- ⚠️ Wix NO deja cambiar nameservers (bloqueados a wixdns) → no se pudo migrar a Cloudflare sin transferir el dominio
- **Carpeta local**: `C:\Users\claud\Documents\Claude\Art 4 Hotel\Pagina Web\art4hotel\`
## Estructura del sitio
- `index.html` — landing (hero, trust strip, grid productos, proceso, wizard de cotización)
- `gracias.html` — página post-envío del formulario
- `productos.json` — datos de productos (generado por el sync)
- `Recursos/catalogo/` — fotos comprimidas (generadas por el sync)
- `Recursos/` — logos SVG, favicons, imágenes de marca
- `sync_catalogo.py` — script de sincronización Hub → sitio
## Formulario de leads
- Wizard de 4 pasos (negocio → productos → detalles → contacto)
- Enviado vía **FormSubmit** a `ventas@art4hotel.com` (sin API key, ya activado)
- Campos con `name=` legibles → llegan estructurados al correo
- `_next` redirige a `gracias.html`
## Catálogo dinámico Hub → Web
Flujo:
```
HUB sync_catalogo.py SITIO (GitHub Pages)
producto 🌐 mostrar_en_web descarga + comprime fotos grid de productos base
ejemplo 🌐 web_ejemplo (7MB → ~80KB, Pillow) ↓ click
+ web_etiqueta (zona) genera productos.json modal storytelling
git commit + push "Así se ve personalizado"
```
### Correr el sync
```
cd "C:\Users\claud\Documents\Claude\Art 4 Hotel\Pagina Web\art4hotel"
python sync_catalogo.py # descarga + JSON + commit + push (publica)
python sync_catalogo.py --no-push # solo local (para revisar antes)
```
Requiere estar en la red del Hub (192.168.50.46). Usa Pillow para comprimir.
### Qué publica
- Productos con `mostrar_en_web=1` Y con foto
- Por producto: nombre, descripcion_web, categoria, personalizaciones (tipos_trabajo), foto base, y **ejemplos** (pedidos con `web_ejemplo=1`)
- Ejemplos: foto + técnica + **web_etiqueta** (zona pública, NUNCA el cliente real → privacidad)
## Diseño / storytelling
- Grid de productos base; cards con foto + badge de ejemplos
- Click → modal: foto base + descripción + personalizaciones + galería "Así se ve personalizado" (ejemplos grandes, `object-fit:contain` para no recortar)
- Branding A4H (Outfit + Playfair + olive/sand/cream), responsive
## Workflow de desarrollo del sitio (preview local)
1. Servidor local: `python -m http.server 8899` en la carpeta del repo → `http://localhost:8899`
2. Editar `index.html` local → refrescar localhost (cambio instantáneo)
3. Cuando esté listo: `git add . && git commit && git push` → GitHub Pages publica en ~1 min
4. Nota: la extensión Claude-in-Chrome NO puede navegar a localhost ni art4hotel.com (dominios restringidos); el preview lo ve el usuario en su navegador.
## Pendientes web
- Filtros por uso (boda/empresa/tienda/hotel) — requiere atributo "uso" en producto
- Secciones por tipo de producto
- Foto base del modal recorta en cuadrado (hacer sin recorte)
- Analytics (Cloudflare Web Analytics o GA)
- Más productos curados + etiquetas de zona en ejemplos

90
backup.py Normal file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
Art4Hotel Hub — Backup diario
Crea snapshot del DB + uploads, conserva últimos N días.
Corre cada noche via cron.
"""
import sqlite3, shutil, datetime, tarfile, sys
from pathlib import Path
BASE = Path("/mnt/iclaude/art4hotel-hub")
DB = BASE / "art4hotel.db"
UPLOADS = BASE / "uploads"
BACKUPS = BASE / "backups"
KEEP_DAYS = 30
def log(msg):
ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{ts}] {msg}", flush=True)
def backup_db(out):
"""Use SQLite online backup (safe with WAL, doesn't lock writers)."""
src = sqlite3.connect(str(DB))
dst = sqlite3.connect(str(out))
with dst:
src.backup(dst)
src.close()
dst.close()
def backup_uploads(out_tar):
if not UPLOADS.exists():
return 0
n = 0
with tarfile.open(str(out_tar), "w:gz") as tar:
for p in UPLOADS.rglob("*"):
if p.is_file():
tar.add(str(p), arcname=str(p.relative_to(UPLOADS.parent)))
n += 1
return n
def prune_old():
cutoff = datetime.datetime.now() - datetime.timedelta(days=KEEP_DAYS)
removed = 0
for d in BACKUPS.iterdir():
if not d.is_dir():
continue
try:
date_part = d.name.split("_")[0]
d_date = datetime.datetime.strptime(date_part, "%Y-%m-%d")
if d_date < cutoff:
shutil.rmtree(d)
removed += 1
except Exception:
pass
return removed
def main():
BACKUPS.mkdir(exist_ok=True)
stamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M")
out_dir = BACKUPS / stamp
out_dir.mkdir(exist_ok=True)
log(f"Backup → {out_dir}")
try:
backup_db(out_dir / "art4hotel.db")
log(f" DB OK ({(out_dir/'art4hotel.db').stat().st_size//1024} KB)")
except Exception as e:
log(f" DB ERROR: {e}")
sys.exit(1)
try:
n = backup_uploads(out_dir / "uploads.tar.gz")
if (out_dir / "uploads.tar.gz").exists():
log(f" uploads OK ({n} archivos, {(out_dir/'uploads.tar.gz').stat().st_size//1024} KB)")
except Exception as e:
log(f" uploads ERROR: {e}")
removed = prune_old()
if removed:
log(f" Eliminados {removed} backup(s) > {KEEP_DAYS} días")
log("Listo.")
if __name__ == "__main__":
main()

37
fix_dup.py Normal file
View File

@@ -0,0 +1,37 @@
"""One-time: renumber duplicate orden_ids. Keep oldest row (lowest id) with original,
rename later rows to MAX+1, MAX+2, ... Does NOT touch uploads/ folders — files stay
attached to the original (lowest id) pedido, which is the desired behavior."""
import sqlite3, sys
DB = 'art4hotel.db'
c = sqlite3.connect(DB)
c.row_factory = sqlite3.Row
dups = [r['orden_id'] for r in c.execute(
"SELECT orden_id FROM ordenes WHERE orden_id GLOB 'ORD-2026-[0-9][0-9][0-9]' "
"GROUP BY orden_id HAVING COUNT(*)>1")]
print(f'Duplicados encontrados: {len(dups)}')
max_num = c.execute(
"SELECT MAX(CAST(SUBSTR(orden_id,10,3) AS INTEGER)) FROM ordenes "
"WHERE orden_id GLOB 'ORD-2026-[0-9][0-9][0-9]'").fetchone()[0] or 0
next_num = max_num + 1
renames = []
for oid in dups:
rows = c.execute("SELECT id, cliente, producto FROM ordenes WHERE orden_id=? ORDER BY id", (oid,)).fetchall()
keep = rows[0]
print(f"\n {oid} KEEP id={keep['id']} ({keep['cliente']} - {keep['producto']})")
for r in rows[1:]:
new_oid = f'ORD-2026-{next_num:03d}'
next_num += 1
print(f" id={r['id']} ({r['cliente']} - {r['producto']}) → {new_oid}")
renames.append((new_oid, r['id']))
if '--apply' in sys.argv:
for new_oid, rid in renames:
c.execute("UPDATE ordenes SET orden_id=? WHERE id=?", (new_oid, rid))
c.commit()
print(f'\nAplicado: {len(renames)} renombrados.')
else:
print(f'\n(dry-run — corre con --apply para aplicar {len(renames)} cambios)')

9771
index.html Normal file

File diff suppressed because it is too large Load Diff

1577
server.py Normal file

File diff suppressed because it is too large Load Diff