1263 lines
43 KiB
Markdown
1263 lines
43 KiB
Markdown
# PymesBot — Especificación Técnica Completa del Proyecto
|
|
|
|
> **Documento de referencia para desarrollo con IA**
|
|
> Versión 1.0 · Febrero 2026 · RVConsultas
|
|
> Este documento contiene TODO lo necesario para construir PymesBot desde cero.
|
|
> No asumas nada que no esté escrito acá. Si algo no está en este documento, preguntá antes de inventar.
|
|
|
|
---
|
|
|
|
## ÍNDICE
|
|
|
|
1. [Qué es PymesBot](#1-qué-es-pymesbot)
|
|
2. [Problema que resuelve](#2-problema-que-resuelve)
|
|
3. [Cómo funciona — visión general](#3-cómo-funciona--visión-general)
|
|
4. [Stack tecnológico completo](#4-stack-tecnológico-completo)
|
|
5. [Arquitectura multi-tenant](#5-arquitectura-multi-tenant)
|
|
6. [Estructura de carpetas en el servidor](#6-estructura-de-carpetas-en-el-servidor)
|
|
7. [Base de datos — esquema completo](#7-base-de-datos--esquema-completo)
|
|
8. [Backend FastAPI — todos los endpoints](#8-backend-fastapi--todos-los-endpoints)
|
|
9. [El agente IA — PicoClaw + Claude](#9-el-agente-ia--picoclaw--claude)
|
|
10. [Dashboard web — especificación UI](#10-dashboard-web--especificación-ui)
|
|
11. [Docker Compose por cliente](#11-docker-compose-por-cliente)
|
|
12. [Templates de configuración](#12-templates-de-configuración)
|
|
13. [Flujo completo de una venta](#13-flujo-completo-de-una-venta)
|
|
14. [Modelo de negocio y precios](#14-modelo-de-negocio-y-precios)
|
|
15. [Roadmap de implementación](#15-roadmap-de-implementación)
|
|
16. [Reglas de desarrollo obligatorias](#16-reglas-de-desarrollo-obligatorias)
|
|
|
|
---
|
|
|
|
## 1. Qué es PymesBot
|
|
|
|
PymesBot es una **plataforma SaaS multi-tenant** que da a negocios minoristas (librerías, kioscos, papelerías, bazares) un asistente de ventas con inteligencia artificial accesible desde el navegador web.
|
|
|
|
El producto final es una **web app** donde el vendedor escribe en lenguaje natural y el bot le responde con información exacta del stock, precios, alternativas y combos. Al confirmar una venta, el stock se descuenta automáticamente.
|
|
|
|
**Quién lo usa:**
|
|
- **Vendedor** (en el mostrador): chat con el bot para atender clientes rápido
|
|
- **Dueño del negocio** (en la misma web): dashboard con estadísticas, gestión de stock, promociones
|
|
|
|
**Quién lo opera:**
|
|
- **RVConsultas** (Guillermo): da de alta clientes nuevos, cobra mensualidad, administra el servidor
|
|
|
|
---
|
|
|
|
## 2. Problema que resuelve
|
|
|
|
La mayoría de negocios PyME argentinos manejan el inventario en papel o Excel sin conexión. Esto genera:
|
|
|
|
1. **Precios aproximados** ("creo que está como $800...") → desconfianza del cliente
|
|
2. **Tiempo perdido** buscando productos en carpetas o cuadernos → el cliente se va
|
|
3. **Cero datos de ventas** → el dueño no sabe qué se vende, cuándo ni cuánto
|
|
|
|
PymesBot resuelve los tres problemas sin pedirle al negocio que aprenda un sistema nuevo. El dueño sigue usando su Excel; el sistema lo importa.
|
|
|
|
---
|
|
|
|
## 3. Cómo funciona — visión general
|
|
|
|
```
|
|
[Vendedor escribe en el chat]
|
|
↓
|
|
[Web app (browser)] ←→ WebSocket ←→ [Backend FastAPI]
|
|
↓
|
|
[PicoClaw agent]
|
|
↓
|
|
[Claude API (Anthropic)]
|
|
↓
|
|
[Herramientas: buscar stock, registrar venta, armar combo]
|
|
↓
|
|
[SQLite del cliente]
|
|
```
|
|
|
|
**Regla de oro: el vendedor completa una consulta en menos de 2 minutos.**
|
|
|
|
Flujo de ejemplo:
|
|
1. Vendedor escribe: `"tenés biromes bic azul?"`
|
|
2. El bot busca en el stock → encuentra: precio $850, stock 23 unidades, variantes azul/rojo/negro
|
|
3. Bot responde: `"Sí, Bic azul a $850 (quedan 23). También hay roja y negra al mismo precio."`
|
|
4. Bot pregunta: `"¿Se concretó la venta? ¿Cuántas unidades?"`
|
|
5. Vendedor: `"2"`
|
|
6. Stock se descuenta de 23 a 21, venta se registra con precio y timestamp
|
|
|
|
---
|
|
|
|
## 4. Stack tecnológico completo
|
|
|
|
### Lenguajes y frameworks
|
|
|
|
| Capa | Tecnología | Versión mínima | Por qué |
|
|
|------|-----------|----------------|---------|
|
|
| Backend API | Python + FastAPI | Python 3.11, FastAPI 0.110 | Asíncrono, rápido, soporte nativo WebSocket |
|
|
| Servidor ASGI | Uvicorn | 0.29 | Standard para FastAPI en producción |
|
|
| Agente IA | PicoClaw (Go) | v0.0.1 | <10MB RAM, arranca en <1s, gateway nativo |
|
|
| Base de datos | SQLite | 3.x (incluido en Python) | Zero config, un archivo, backup trivial |
|
|
| Templates HTML | Jinja2 | 3.x | Incluido en FastAPI |
|
|
| Frontend | HTML + CSS + JS vanilla | ES2020 | Sin build step, carga instantánea |
|
|
| Reverse proxy | Nginx | 1.24+ | Wildcard SSL, reverse proxy, performance |
|
|
| SSL | Certbot + Let's Encrypt | Último | Gratis, auto-renovación |
|
|
| Contenedores | Docker + Docker Compose | Docker 24+, Compose v2 | Aislamiento por cliente |
|
|
|
|
### Servicios externos
|
|
|
|
| Servicio | Uso | Quién paga |
|
|
|----------|-----|-----------|
|
|
| Anthropic API (Claude) | El cerebro del bot | RVConsultas (costo absorbido en precio mensual) |
|
|
| Modelo a usar | `claude-sonnet-4-5-20250929` | — |
|
|
| VPS | Servidor de producción | RVConsultas |
|
|
|
|
### VPS recomendado
|
|
|
|
- **RAM:** 16 GB
|
|
- **CPU:** 6 núcleos
|
|
- **Disco:** 100 GB SSD
|
|
- **OS:** Ubuntu 24.04 LTS
|
|
- **Capacidad:** ~150 clientes simultáneos cómodos
|
|
|
|
---
|
|
|
|
## 5. Arquitectura multi-tenant
|
|
|
|
### Concepto fundamental
|
|
|
|
Cada cliente (negocio) tiene su propio **subdominio** bajo `rvconsultas.com`:
|
|
|
|
```
|
|
castillo.rvconsultas.com → Librería Castillo
|
|
reyes.rvconsultas.com → Librería Reyes
|
|
nuevocliente.rvconsultas.com → cualquier negocio nuevo
|
|
```
|
|
|
|
Cada subdominio es un **entorno completamente aislado**:
|
|
- Su propio contenedor Docker (backend FastAPI)
|
|
- Su propio agente PicoClaw
|
|
- Su propia base de datos SQLite
|
|
- Su propio inventario y configuración
|
|
|
|
**Un cliente NUNCA puede ver datos de otro cliente. Jamás. Este es un requisito no negociable.**
|
|
|
|
### Diagrama de arquitectura
|
|
|
|
```
|
|
Internet
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Nginx (host) │
|
|
│ *.rvconsultas.com → Let's Encrypt wildcard SSL │
|
|
│ Reverse proxy → puerto interno por cliente │
|
|
└────────┬────────────────────┬───────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
castillo.rvconsultas.com reyes.rvconsultas.com
|
|
Puerto 8201 Puerto 8202
|
|
│ │
|
|
┌────────┴──────┐ ┌────────┴──────┐
|
|
│ Docker │ │ Docker │
|
|
│ Container A │ │ Container B │
|
|
│ │ │ │
|
|
│ FastAPI │ │ FastAPI │
|
|
│ PicoClaw │ │ PicoClaw │
|
|
│ SQLite DB │ │ SQLite DB │
|
|
└───────────────┘ └───────────────┘
|
|
│ │
|
|
└────────┬───────────┘
|
|
▼
|
|
Anthropic API
|
|
(claude-sonnet-4-5-20250929)
|
|
Una sola API key de RVConsultas
|
|
```
|
|
|
|
### Nginx — configuración de subdominio por cliente
|
|
|
|
Cada cliente tiene su archivo `/etc/nginx/conf.d/{slug}.conf`:
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name castillo.rvconsultas.com;
|
|
# Certbot agrega el bloque HTTPS automáticamente
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8201;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Estructura de carpetas en el servidor
|
|
|
|
```
|
|
/opt/pymesbot/ ← raíz de toda la plataforma
|
|
│
|
|
├── .env.global ← API key de Anthropic (una sola, compartida)
|
|
│ # Contenido:
|
|
│ # ANTHROPIC_API_KEY=sk-ant-...
|
|
│
|
|
├── nginx/
|
|
│ └── conf.d/
|
|
│ ├── castillo.conf ← bloque Nginx generado por el instalador
|
|
│ └── reyes.conf
|
|
│
|
|
├── scripts/
|
|
│ ├── nuevo_cliente.sh ← script de onboarding
|
|
│ └── backup.sh ← backup diario de todas las DBs
|
|
│
|
|
├── castillo/ ← carpeta completa de UN cliente
|
|
│ ├── docker-compose.yml ← levanta los contenedores de este cliente
|
|
│ ├── .env ← variables de entorno (generadas por instalador)
|
|
│ │
|
|
│ ├── picoclaw/
|
|
│ │ └── config.json ← config de PicoClaw: API key + system prompt
|
|
│ │
|
|
│ ├── backend/
|
|
│ │ ├── Dockerfile ← imagen del backend FastAPI
|
|
│ │ ├── requirements.txt
|
|
│ │ ├── main.py ← FastAPI app principal
|
|
│ │ ├── db.py ← helpers de SQLite
|
|
│ │ ├── tools.py ← endpoints que llama PicoClaw como herramientas
|
|
│ │ ├── dashboard.py ← rutas del dashboard web
|
|
│ │ ├── auth.py ← autenticación simple (PIN vendedor / admin)
|
|
│ │ ├── models.py ← Pydantic models
|
|
│ │ └── templates/
|
|
│ │ ├── base.html ← layout base (navbar, CSS global)
|
|
│ │ ├── chat.html ← vista del vendedor (mostrador)
|
|
│ │ └── admin.html ← vista del dueño (dashboard)
|
|
│ │
|
|
│ └── data/
|
|
│ ├── stock.db ← base de datos SQLite del cliente
|
|
│ └── uploads/ ← Excels subidos por el dueño
|
|
│
|
|
└── reyes/ ← ídem para otro cliente
|
|
└── ...
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Base de datos — esquema completo
|
|
|
|
Cada cliente tiene su propio archivo `stock.db`. El esquema se crea con este SQL al inicializar:
|
|
|
|
```sql
|
|
-- ═══════════════════════════════════════════════════════
|
|
-- TABLA: productos
|
|
-- ═══════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS productos (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
nombre TEXT NOT NULL, -- "Birome Bic"
|
|
marca TEXT, -- "Bic"
|
|
categoria TEXT NOT NULL, -- "escritura"
|
|
precio REAL NOT NULL, -- 850.00
|
|
stock INTEGER NOT NULL DEFAULT 0, -- 23
|
|
variantes TEXT, -- JSON: {"color":["azul","rojo"],"talle":["M","L"]}
|
|
codigo TEXT, -- código interno o EAN (opcional)
|
|
activo INTEGER NOT NULL DEFAULT 1, -- 1=activo, 0=deshabilitado (no aparece en búsquedas)
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
-- Índices para búsqueda rápida
|
|
CREATE INDEX IF NOT EXISTS idx_productos_nombre ON productos(nombre);
|
|
CREATE INDEX IF NOT EXISTS idx_productos_categoria ON productos(categoria);
|
|
CREATE INDEX IF NOT EXISTS idx_productos_activo ON productos(activo);
|
|
|
|
-- ═══════════════════════════════════════════════════════
|
|
-- TABLA: ventas
|
|
-- ═══════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS ventas (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
producto_id INTEGER NOT NULL REFERENCES productos(id),
|
|
cantidad INTEGER NOT NULL,
|
|
precio_vendido REAL NOT NULL, -- precio al momento de la venta (puede diferir del actual)
|
|
vendedor TEXT, -- nombre del vendedor (optativo)
|
|
notas TEXT, -- notas adicionales (optativo)
|
|
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_ventas_producto_id ON ventas(producto_id);
|
|
CREATE INDEX IF NOT EXISTS idx_ventas_timestamp ON ventas(timestamp);
|
|
|
|
-- ═══════════════════════════════════════════════════════
|
|
-- TABLA: promociones
|
|
-- ═══════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS promociones (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
nombre TEXT NOT NULL, -- "Vuelta al cole 20% off"
|
|
tipo TEXT NOT NULL, -- "descuento_pct" | "descuento_fijo" | "precio_especial"
|
|
valor REAL NOT NULL, -- 20 (para pct) | 500 (para fijo) | 1500 (para precio_especial)
|
|
productos TEXT, -- JSON array de product IDs: [1,2,3] o NULL = todos
|
|
categorias TEXT, -- JSON array de categorías: ["escritura"] o NULL = todas
|
|
activa INTEGER NOT NULL DEFAULT 1,
|
|
fecha_inicio TEXT, -- NULL = siempre activa
|
|
fecha_fin TEXT, -- NULL = sin vencimiento
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
-- ═══════════════════════════════════════════════════════
|
|
-- TABLA: config
|
|
-- Configuración del negocio como key-value
|
|
-- ═══════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS config (
|
|
clave TEXT PRIMARY KEY,
|
|
valor TEXT NOT NULL
|
|
);
|
|
|
|
-- Valores por defecto que se insertan al crear el negocio
|
|
INSERT OR IGNORE INTO config (clave, valor) VALUES
|
|
('nombre_negocio', 'Mi Negocio'), -- se sobreescribe con el nombre real
|
|
('moneda', 'ARS'),
|
|
('moneda_simbolo', '$'),
|
|
('combo_categorias', '["escritura","cuadernos","geometria","colores"]'),
|
|
('alerta_stock_minimo', '5'), -- notificar cuando stock < este número
|
|
('vendedor_pin', '1234'), -- PIN para acceso rápido al chat
|
|
('admin_password', ''), -- se genera aleatoriamente al instalar
|
|
('rubro', 'libreria'); -- libreria | kiosco | bazar | otro
|
|
```
|
|
|
|
### Notas importantes sobre la DB
|
|
|
|
- **SQLite es suficiente para este caso de uso.** Un negocio PyME rara vez tiene más de 5.000 productos y algunas docenas de ventas por hora. SQLite maneja esto sin problema.
|
|
- **Nunca usar ORM pesados como SQLAlchemy para esto.** Usar `sqlite3` de la stdlib de Python directamente o `aiosqlite` para las queries async. Simple y sin magia.
|
|
- **El campo `variantes`** es un JSON string. Ejemplo: `'{"color": ["azul", "rojo", "negro"], "talle": ["M", "L", "XL"]}'`. El backend lo parsea con `json.loads()`.
|
|
- **El campo `precio_vendido` en ventas** es importante: el precio puede cambiar con el tiempo. Hay que guardar el precio al momento de la venta, no una referencia al precio actual del producto.
|
|
|
|
---
|
|
|
|
## 8. Backend FastAPI — todos los endpoints
|
|
|
|
### Archivo: `main.py`
|
|
|
|
```python
|
|
# Estructura básica de main.py
|
|
from fastapi import FastAPI, WebSocket, UploadFile, Depends
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
app = FastAPI(title="PymesBot Backend")
|
|
|
|
# Montar rutas
|
|
app.include_router(stock_router, prefix="/stock", tags=["stock"])
|
|
app.include_router(ventas_router, prefix="/venta", tags=["ventas"])
|
|
app.include_router(combo_router, prefix="/combo", tags=["combos"])
|
|
app.include_router(promo_router, prefix="/promo", tags=["promociones"])
|
|
app.include_router(stats_router, prefix="/stats", tags=["estadísticas"])
|
|
app.include_router(dashboard_router, prefix="", tags=["dashboard"])
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "ok", "negocio": get_config("nombre_negocio")}
|
|
```
|
|
|
|
---
|
|
|
|
### Endpoints de Stock (`/stock`)
|
|
|
|
#### `GET /stock/search`
|
|
|
|
Búsqueda de productos en lenguaje natural. Este es el endpoint más importante, lo llama PicoClaw.
|
|
|
|
**Parámetros query:**
|
|
- `q` (string, requerido): query de búsqueda libre. Ej: `"birome bic azul"`, `"cuaderno rayado chico"`
|
|
- `limit` (int, opcional, default: 5): máximo de resultados
|
|
|
|
**Lógica de búsqueda (implementar en este orden de prioridad):**
|
|
1. Búsqueda exacta por nombre (LIKE `%q%`)
|
|
2. Búsqueda por palabras clave individuales (split por espacios, AND de todos los términos)
|
|
3. Si hay menos de 3 resultados: búsqueda OR (al menos una palabra coincide)
|
|
4. Solo devolver productos con `activo = 1`
|
|
|
|
**Respuesta exitosa (200):**
|
|
```json
|
|
{
|
|
"resultados": [
|
|
{
|
|
"id": 42,
|
|
"nombre": "Birome Bic Cristal",
|
|
"marca": "Bic",
|
|
"categoria": "escritura",
|
|
"precio": 850.00,
|
|
"precio_formateado": "$850",
|
|
"stock": 23,
|
|
"variantes": {"color": ["azul", "rojo", "negro"]},
|
|
"hay_stock": true,
|
|
"promo_activa": null
|
|
}
|
|
],
|
|
"total": 1,
|
|
"query": "birome bic azul"
|
|
}
|
|
```
|
|
|
|
**Si no hay resultados:**
|
|
```json
|
|
{
|
|
"resultados": [],
|
|
"total": 0,
|
|
"query": "birome bic azul",
|
|
"sugerencias": ["Birome Bic Cristal", "Birome Faber Castell"]
|
|
}
|
|
```
|
|
> Las sugerencias son productos de la misma categoría estimada con stock disponible.
|
|
|
|
---
|
|
|
|
#### `GET /stock/producto/{id}`
|
|
|
|
Detalle completo de un producto por ID.
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"id": 42,
|
|
"nombre": "Birome Bic Cristal",
|
|
"marca": "Bic",
|
|
"categoria": "escritura",
|
|
"precio": 850.00,
|
|
"stock": 23,
|
|
"variantes": {"color": ["azul", "rojo", "negro"]},
|
|
"codigo": "7501031311309",
|
|
"activo": true,
|
|
"created_at": "2026-01-15T10:30:00",
|
|
"updated_at": "2026-02-10T14:22:00"
|
|
}
|
|
```
|
|
|
|
**Error 404:**
|
|
```json
|
|
{"detail": "Producto con id 42 no encontrado"}
|
|
```
|
|
|
|
---
|
|
|
|
#### `PUT /stock/producto/{id}`
|
|
|
|
Actualiza precio o stock de un producto existente. Solo el dueño puede llamar esto (auth requerida).
|
|
|
|
**Body (todos opcionales, solo se actualiza lo que se envía):**
|
|
```json
|
|
{
|
|
"precio": 950.00,
|
|
"stock": 30,
|
|
"activo": true
|
|
}
|
|
```
|
|
|
|
**Respuesta (200):** el producto actualizado completo.
|
|
|
|
---
|
|
|
|
#### `POST /stock/importar`
|
|
|
|
Importa o reemplaza el inventario completo desde un archivo Excel o CSV. Solo el dueño puede llamar esto.
|
|
|
|
**Form data:**
|
|
- `file`: archivo `.xlsx` o `.csv`
|
|
- `modo`: `"reemplazar"` (borra todo y reimporta) | `"actualizar"` (solo actualiza precios/stock de los que ya existen, agrega los nuevos)
|
|
|
|
**Columnas esperadas en el Excel/CSV (detección automática, case-insensitive):**
|
|
- `nombre` o `producto` o `descripcion` → campo `nombre` (REQUERIDO)
|
|
- `precio` o `precio_venta` o `price` → campo `precio` (REQUERIDO)
|
|
- `stock` o `cantidad` o `qty` → campo `stock` (default: 0 si no está)
|
|
- `categoria` o `rubro` o `category` → campo `categoria` (default: "general")
|
|
- `marca` o `brand` → campo `marca` (opcional)
|
|
- `codigo` o `ean` o `barcode` → campo `codigo` (opcional)
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"importados": 245,
|
|
"actualizados": 12,
|
|
"errores": 0,
|
|
"errores_detalle": []
|
|
}
|
|
```
|
|
|
|
**Respuesta con errores (200, pero con detalle de errores):**
|
|
```json
|
|
{
|
|
"importados": 240,
|
|
"actualizados": 0,
|
|
"errores": 5,
|
|
"errores_detalle": [
|
|
{"fila": 12, "razon": "precio inválido: 'consultar'"},
|
|
{"fila": 34, "razon": "nombre vacío"}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /stock/alertas`
|
|
|
|
Devuelve productos con stock por debajo del mínimo configurado.
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"alertas": [
|
|
{"id": 12, "nombre": "Goma Staedtler", "stock": 3, "minimo": 5},
|
|
{"id": 44, "nombre": "Regla 30cm", "stock": 1, "minimo": 5}
|
|
],
|
|
"total": 2
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Endpoints de Ventas (`/venta`)
|
|
|
|
#### `POST /venta/confirmar`
|
|
|
|
Registra una venta y descuenta el stock. Lo llama PicoClaw después de confirmar con el vendedor.
|
|
|
|
**Body:**
|
|
```json
|
|
{
|
|
"producto_id": 42,
|
|
"cantidad": 2,
|
|
"precio_vendido": 850.00,
|
|
"vendedor": "María"
|
|
}
|
|
```
|
|
|
|
**Validaciones antes de ejecutar:**
|
|
1. El producto existe y está activo
|
|
2. `cantidad > 0`
|
|
3. `stock actual >= cantidad` (si el stock quedaría negativo, devolver error)
|
|
4. Si hay promo activa aplicable, usar ese precio (pero respetar el precio_vendido enviado si difiere)
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"venta_id": 891,
|
|
"producto": "Birome Bic Cristal",
|
|
"cantidad": 2,
|
|
"precio_vendido": 850.00,
|
|
"total": 1700.00,
|
|
"stock_anterior": 23,
|
|
"stock_nuevo": 21,
|
|
"timestamp": "2026-02-14T15:42:00"
|
|
}
|
|
```
|
|
|
|
**Error 400 (stock insuficiente):**
|
|
```json
|
|
{
|
|
"detail": "Stock insuficiente. Stock actual: 1, solicitado: 2."
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /venta/historial`
|
|
|
|
Lista de ventas con filtros opcionales.
|
|
|
|
**Parámetros query (todos opcionales):**
|
|
- `fecha_desde`: ISO date string. Default: hoy a las 00:00
|
|
- `fecha_hasta`: ISO date string. Default: ahora
|
|
- `producto_id`: filtrar por producto
|
|
- `vendedor`: filtrar por vendedor
|
|
- `limit`: default 100, max 1000
|
|
- `offset`: para paginación
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"ventas": [
|
|
{
|
|
"id": 891,
|
|
"producto_id": 42,
|
|
"producto_nombre": "Birome Bic Cristal",
|
|
"cantidad": 2,
|
|
"precio_vendido": 850.00,
|
|
"total": 1700.00,
|
|
"vendedor": "María",
|
|
"timestamp": "2026-02-14T15:42:00"
|
|
}
|
|
],
|
|
"total": 1,
|
|
"suma_total": 1700.00
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Endpoints de Estadísticas (`/stats`)
|
|
|
|
#### `GET /stats/ventas`
|
|
|
|
Resumen de ventas para el dashboard del dueño.
|
|
|
|
**Parámetros query:**
|
|
- `periodo`: `"hoy"` | `"semana"` | `"mes"` (default: `"hoy"`)
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"periodo": "hoy",
|
|
"total_ventas": 47,
|
|
"total_pesos": 38500.00,
|
|
"ticket_promedio": 819.15,
|
|
"productos_mas_vendidos": [
|
|
{"nombre": "Birome Bic Cristal", "cantidad": 12, "total": 10200.00},
|
|
{"nombre": "Cuaderno Rivadavia", "cantidad": 8, "total": 12000.00}
|
|
],
|
|
"comparacion_periodo_anterior": {
|
|
"total_pesos_anterior": 31200.00,
|
|
"variacion_pct": 23.4
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Endpoints de Combos (`/combo`)
|
|
|
|
#### `POST /combo/armar`
|
|
|
|
Genera una lista de productos sugeridos para un presupuesto dado. Lo llama PicoClaw.
|
|
|
|
**Body:**
|
|
```json
|
|
{
|
|
"presupuesto": 300000,
|
|
"edad": 7,
|
|
"genero": "nena",
|
|
"categorias": ["escritura", "cuadernos", "geometria", "colores"],
|
|
"incluir_solo_con_stock": true
|
|
}
|
|
```
|
|
|
|
**Notas sobre `genero`:**
|
|
- `"nena"`: priorizar colores llamativos (rosa, lila, rojo), evitar nombres genéricamente masculinos en nombres de productos si hubiera
|
|
- `"neno"`: priorizar azul, verde, colores neutros
|
|
- `null` o ausente: sin preferencia de género
|
|
|
|
**Notas sobre `edad`:**
|
|
- `<= 6 años`: primaria inicial → lápices de colores grandes, plastilina, sin tijeras de punta
|
|
- `7-10 años`: primaria general → juego de geometría, cuadernos doble raya, colores largos
|
|
- `11-13 años`: primaria superior → cuadernos cuadriculados, compás, colores profesionales
|
|
- `>= 14 años`: secundaria → calculadora, carpeta, birome, cuaderno universitario
|
|
|
|
**Lógica del armado de combo:**
|
|
1. Filtrar productos por categorías solicitadas y con stock > 0
|
|
2. Ordenar por "esencialidad" (el backend tiene una lista hardcodeada de productos esenciales por edad)
|
|
3. Ir sumando productos desde los más esenciales hasta llegar al 95% del presupuesto
|
|
4. Si el presupuesto no alcanza para un producto esencial, marcarlo como "no incluido" pero mostrarlo igual
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"presupuesto": 300000,
|
|
"total_combo": 287500,
|
|
"vuelto": 12500,
|
|
"productos": [
|
|
{
|
|
"id": 42,
|
|
"nombre": "Birome Bic Cristal (azul)",
|
|
"precio": 850,
|
|
"cantidad": 2,
|
|
"subtotal": 1700,
|
|
"incluido": true,
|
|
"esencial": true
|
|
},
|
|
{
|
|
"id": 78,
|
|
"nombre": "Cuaderno Rivadavia A4",
|
|
"precio": 2500,
|
|
"cantidad": 3,
|
|
"subtotal": 7500,
|
|
"incluido": true,
|
|
"esencial": true
|
|
}
|
|
],
|
|
"no_incluidos": []
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Endpoints de Promociones (`/promo`)
|
|
|
|
#### `GET /promo/activas`
|
|
|
|
Lista de promociones vigentes hoy.
|
|
|
|
**Respuesta (200):**
|
|
```json
|
|
{
|
|
"promociones": [
|
|
{
|
|
"id": 3,
|
|
"nombre": "Vuelta al cole 20% off escritura",
|
|
"tipo": "descuento_pct",
|
|
"valor": 20,
|
|
"categorias": ["escritura"],
|
|
"productos": null,
|
|
"fecha_fin": "2026-03-15"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### `POST /promo/crear`
|
|
|
|
Crea una nueva promoción. Solo el dueño puede llamar esto.
|
|
|
|
**Body:**
|
|
```json
|
|
{
|
|
"nombre": "Vuelta al cole 20% off escritura",
|
|
"tipo": "descuento_pct",
|
|
"valor": 20,
|
|
"categorias": ["escritura"],
|
|
"productos": null,
|
|
"fecha_inicio": null,
|
|
"fecha_fin": "2026-03-15"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### WebSocket de Chat (`/chat`)
|
|
|
|
#### `WebSocket /chat/ws/{session_id}`
|
|
|
|
El canal de comunicación entre el frontend del vendedor y el agente PicoClaw.
|
|
|
|
**Flujo del WebSocket:**
|
|
|
|
```
|
|
Frontend Backend (FastAPI) PicoClaw
|
|
| | |
|
|
|-- connect ws/session_abc ---> | |
|
|
| | |
|
|
|-- { "msg": "tenés bic?" } --> | |
|
|
| |-- llamar PicoClaw -------> |
|
|
| | |-- GET /stock/search?q=bic
|
|
| |<-- respuesta parcial ----- |
|
|
|<-- { "typing": true } ------- | |
|
|
| |<-- respuesta final ------- |
|
|
|<-- { "msg": "Sí, Bic...", "typing": false } ------------- |
|
|
```
|
|
|
|
**Formato de mensajes del frontend al backend:**
|
|
```json
|
|
{ "msg": "texto del vendedor", "session_id": "abc123" }
|
|
```
|
|
|
|
**Formato de mensajes del backend al frontend:**
|
|
```json
|
|
{ "msg": "texto de respuesta del bot", "typing": false }
|
|
{ "typing": true }
|
|
{ "error": "descripción del error" }
|
|
```
|
|
|
|
**Historial de conversación:**
|
|
- El backend mantiene el historial de la sesión en memoria (dict con `session_id` como clave)
|
|
- El historial se descarta después de 30 minutos de inactividad
|
|
- Máximo 20 turnos de historial por sesión (para no explotar el context window)
|
|
|
|
---
|
|
|
|
## 9. El agente IA — PicoClaw + Claude
|
|
|
|
### Qué es PicoClaw
|
|
|
|
PicoClaw (github.com/sipeed/picoclaw) es un agente IA escrito en Go. Es extremadamente liviano:
|
|
- **RAM:** < 10 MB por instancia
|
|
- **Startup:** < 1 segundo
|
|
- **Arquitectura:** binario Go auto-contenido, sin dependencias externas
|
|
- Se conecta a la API de Claude (Anthropic) o cualquier API compatible con OpenAI
|
|
|
|
En nuestra arquitectura, PicoClaw corre como un proceso dentro del contenedor del cliente. El backend FastAPI lo llama internamente cuando llega un mensaje de chat.
|
|
|
|
### Config de PicoClaw (`picoclaw/config.json`)
|
|
|
|
```json
|
|
{
|
|
"agents": {
|
|
"defaults": {
|
|
"model": "claude-sonnet-4-5-20250929",
|
|
"max_tokens": 1000,
|
|
"temperature": 0.3,
|
|
"max_tool_iterations": 5,
|
|
"system": "SYSTEM PROMPT AQUÍ (ver abajo)"
|
|
}
|
|
},
|
|
"providers": {
|
|
"anthropic": {
|
|
"api_key": "${ANTHROPIC_API_KEY}"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### System Prompt completo
|
|
|
|
El system prompt se personaliza por negocio. Este es el template:
|
|
|
|
```
|
|
Sos el asistente de ventas de {NOMBRE_NEGOCIO}, una {RUBRO} ubicada en Argentina.
|
|
Tu trabajo es ayudar al vendedor a atender clientes de forma rápida y con información exacta.
|
|
|
|
## HERRAMIENTAS DISPONIBLES
|
|
Tenés acceso a estas herramientas. SIEMPRE usarlas antes de responder sobre stock o precios:
|
|
- `buscar_stock`: busca productos en el inventario por nombre o descripción
|
|
- `confirmar_venta`: registra una venta y descuenta el stock
|
|
- `armar_combo`: genera una lista de productos para un presupuesto dado
|
|
- `ver_promociones`: muestra las promociones activas de hoy
|
|
|
|
## REGLAS OBLIGATORIAS (nunca violarlas)
|
|
|
|
1. **NUNCA des precios aproximados.** Siempre usá `buscar_stock` para obtener el precio exacto.
|
|
Si el tool no devuelve precio, decí que no tenés ese dato, no inventes.
|
|
|
|
2. **Si no hay stock de un producto**, ofrecé SIEMPRE la alternativa más cercana que sí haya stock.
|
|
Ej: "No tenemos Bic azul, pero sí tenemos Faber roja a $750."
|
|
|
|
3. **Al armar combos**, SIEMPRE preguntá antes:
|
|
- ¿Cuánto es el presupuesto? (número exacto)
|
|
- ¿Para qué edad es?
|
|
- ¿Es para nene o nena? (optativo, decile que puede no responder)
|
|
|
|
4. **Al final de cada consulta exitosa**, preguntá: "¿Se concretó la venta?"
|
|
Si dicen sí, preguntá cuántas unidades y llamá a `confirmar_venta`.
|
|
|
|
5. **Respondé SIEMPRE en español argentino coloquial pero profesional.**
|
|
Usá "vos", "tenés", "querés". No uses "usted" ni español neutro.
|
|
|
|
6. **Sé conciso.** Una respuesta de chat, no un ensayo. Máximo 3-4 líneas por respuesta.
|
|
|
|
7. **Nunca menciones información de otros negocios, competidores ni otras sucursales.**
|
|
|
|
## EJEMPLOS DE RESPUESTAS CORRECTAS
|
|
|
|
Vendedor: "tenés birome bic azul?"
|
|
Bot: [llama buscar_stock con "birome bic azul"]
|
|
Bot: "Sí, tenemos Bic Cristal azul a $850. Quedan 23. ¿Se vendió?"
|
|
|
|
Vendedor: "no hay regla 30cm"
|
|
Bot: [llama buscar_stock con "regla 30cm"]
|
|
Bot: "No tenemos regla de 30cm por ahora 😕 Pero sí hay de 20cm (Maped, $650, stock 8). ¿Te sirve esa?"
|
|
|
|
Vendedor: "una nena me pregunta para útiles, tiene $300000"
|
|
Bot: "¡Perfecto! ¿Cuántos años tiene? Y si querés podés decirme si tiene algún color preferido."
|
|
```
|
|
|
|
### Herramientas que PicoClaw puede llamar
|
|
|
|
PicoClaw llama los endpoints del backend FastAPI como "tools". La configuración de tools se pasa en el context:
|
|
|
|
```json
|
|
{
|
|
"tools": [
|
|
{
|
|
"name": "buscar_stock",
|
|
"description": "Busca productos en el inventario. Úsala siempre antes de dar precios o hablar de stock.",
|
|
"input_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": { "type": "string", "description": "Nombre o descripción del producto" }
|
|
},
|
|
"required": ["query"]
|
|
}
|
|
},
|
|
{
|
|
"name": "confirmar_venta",
|
|
"description": "Registra una venta y descuenta el stock. Solo llamar cuando el vendedor confirme.",
|
|
"input_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"producto_id": { "type": "integer" },
|
|
"cantidad": { "type": "integer" },
|
|
"precio_vendido": { "type": "number" }
|
|
},
|
|
"required": ["producto_id", "cantidad", "precio_vendido"]
|
|
}
|
|
},
|
|
{
|
|
"name": "armar_combo",
|
|
"description": "Genera una lista de productos para un presupuesto dado.",
|
|
"input_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"presupuesto": { "type": "number" },
|
|
"edad": { "type": "integer" },
|
|
"genero": { "type": "string", "enum": ["nena", "neno", null] }
|
|
},
|
|
"required": ["presupuesto"]
|
|
}
|
|
},
|
|
{
|
|
"name": "ver_promociones",
|
|
"description": "Muestra las promociones activas de hoy.",
|
|
"input_schema": { "type": "object", "properties": {} }
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Dashboard web — especificación UI
|
|
|
|
### Vista Vendedor (chat del mostrador)
|
|
|
|
**URL:** `https://castillo.rvconsultas.com/`
|
|
**Acceso:** PIN de 4 dígitos (configurable por el dueño)
|
|
|
|
**Layout:**
|
|
```
|
|
┌──────────────────────────────────────────┐
|
|
│ 🛒 Librería Castillo [vendedor: María]│ ← header
|
|
├─────────────────────┬────────────────────┤
|
|
│ │ 📊 Hoy │
|
|
│ CHAT │ Ventas: 47 │
|
|
│ │ Total: $38.500 │
|
|
│ Bot: Hola! ¿Qué │ │
|
|
│ necesitás buscar? │ ⚠️ Stock bajo (2) │
|
|
│ │ - Goma Staedtler │
|
|
│ Vend: tenés bic? │ - Regla 20cm │
|
|
│ │ │
|
|
│ Bot: Sí, Bic azul │ │
|
|
│ $850. Quedan 23. │ │
|
|
│ ¿Se vendió? │ │
|
|
│ │ │
|
|
│ [_______________] │ │
|
|
│ [ Enviar ] │ │
|
|
└─────────────────────┴────────────────────┘
|
|
```
|
|
|
|
**Características del chat:**
|
|
- El historial de la sesión actual se muestra en pantalla
|
|
- Los mensajes del bot tienen fondo diferente a los del vendedor
|
|
- Indicador de "escribiendo..." mientras el bot procesa
|
|
- Botones de acción rápida cuando corresponde: `[✓ Confirmar venta]` `[Ver alternativas]` `[Nuevo combo]`
|
|
- En móvil: el panel de resumen del día se colapsa. Solo el chat visible.
|
|
- **Responsive:** debe funcionar bien en tablet 10" y en PC de escritorio
|
|
|
|
**Autenticación:**
|
|
- Al entrar, pide el PIN de 4 dígitos
|
|
- El PIN correcto abre el chat
|
|
- No hay "logout", el PIN simplemente da acceso
|
|
|
|
---
|
|
|
|
### Vista Dueño / Admin
|
|
|
|
**URL:** `https://castillo.rvconsultas.com/admin`
|
|
**Acceso:** usuario `admin` + contraseña generada al instalar
|
|
|
|
**Secciones del admin:**
|
|
|
|
#### 📊 Dashboard principal
|
|
- Total vendido hoy / esta semana / este mes (switcher de período)
|
|
- Gráfico de barras de ventas por día (últimos 7 días)
|
|
- Top 5 productos más vendidos (tabla)
|
|
- Comparación con período anterior (% de variación)
|
|
|
|
#### 📦 Gestión de stock
|
|
- Tabla con todos los productos: nombre, categoría, precio, stock, activo
|
|
- Edición inline: hacer click en precio o stock para editar
|
|
- Filtros: por categoría, por nombre (búsqueda), por "stock bajo"
|
|
- Botón "Importar Excel": abre modal para subir archivo
|
|
|
|
#### 🎯 Promociones
|
|
- Lista de promociones activas/inactivas
|
|
- Formulario para crear nueva promo:
|
|
- Nombre
|
|
- Tipo: `% descuento` / `monto fijo` / `precio especial`
|
|
- Valor
|
|
- Aplicable a: todas las categorías / categorías específicas / productos específicos
|
|
- Fecha de vencimiento (opcional)
|
|
- Toggle para activar/desactivar
|
|
|
|
#### 📋 Historial de ventas
|
|
- Tabla filtrable por fecha y producto
|
|
- Filtro de rango de fechas (date picker)
|
|
- Exportar a CSV (botón)
|
|
|
|
#### ⚙️ Configuración
|
|
- Nombre del negocio
|
|
- Moneda
|
|
- Umbral de alerta de stock mínimo
|
|
- Cambiar PIN del vendedor
|
|
- Cambiar contraseña del admin
|
|
|
|
---
|
|
|
|
## 11. Docker Compose por cliente
|
|
|
|
Cada cliente tiene su propio `docker-compose.yml`. Se genera automáticamente con el instalador.
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
|
|
backend_castillo:
|
|
build:
|
|
context: ./backend
|
|
dockerfile: Dockerfile
|
|
container_name: pymesbot_castillo
|
|
restart: always
|
|
ports:
|
|
- "${PUERTO}:8000" # PUERTO viene del .env, ej: 8201
|
|
volumes:
|
|
- ./data:/app/data # la DB SQLite vive acá
|
|
- ./backend:/app # para desarrollo (en prod, solo copiar)
|
|
env_file:
|
|
- .env
|
|
networks:
|
|
- pymesbot_net
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
picoclaw_castillo:
|
|
image: ghcr.io/sipeed/picoclaw:latest
|
|
# Alternativa si la imagen no está disponible: build local del binario Go
|
|
container_name: picoclaw_castillo
|
|
restart: always
|
|
volumes:
|
|
- ./picoclaw:/root/.picoclaw # config.json de PicoClaw
|
|
- ./data:/data:ro # acceso READ-ONLY a los datos (solo el backend escribe)
|
|
depends_on:
|
|
backend_castillo:
|
|
condition: service_healthy
|
|
networks:
|
|
- pymesbot_net
|
|
environment:
|
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
|
|
|
networks:
|
|
pymesbot_net:
|
|
external: true # creada una vez por el instalador, compartida entre todos los clientes
|
|
```
|
|
|
|
### Dockerfile del backend (`backend/Dockerfile`)
|
|
|
|
```dockerfile
|
|
FROM python:3.11-slim
|
|
|
|
WORKDIR /app
|
|
|
|
# Instalar dependencias del sistema
|
|
RUN apt-get update && apt-get install -y \
|
|
curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Copiar e instalar dependencias Python
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
# Copiar código
|
|
COPY . .
|
|
|
|
# Crear directorio de datos
|
|
RUN mkdir -p /app/data/uploads
|
|
|
|
# Exponer puerto
|
|
EXPOSE 8000
|
|
|
|
# Comando de inicio
|
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
|
|
```
|
|
|
|
### requirements.txt
|
|
|
|
```
|
|
fastapi==0.110.0
|
|
uvicorn[standard]==0.29.0
|
|
jinja2==3.1.3
|
|
python-multipart==0.0.9
|
|
aiosqlite==0.20.0
|
|
openpyxl==3.1.2
|
|
pandas==2.2.0
|
|
anthropic==0.25.0
|
|
python-dotenv==1.0.1
|
|
aiofiles==23.2.1
|
|
httpx==0.27.0
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Templates de configuración
|
|
|
|
### `.env` por cliente
|
|
|
|
```bash
|
|
# Generado automáticamente por el instalador
|
|
ANTHROPIC_API_KEY=sk-ant-... # copiada del .env.global
|
|
CLIENTE_NOMBRE=Librería Castillo
|
|
CLIENTE_ID=castillo
|
|
PUERTO=8201
|
|
ADMIN_PASSWORD=Xk7mQ2p # generada aleatoriamente
|
|
VENDOR_PIN=4821 # generada aleatoriamente
|
|
DOMINIO_BASE=rvconsultas.com
|
|
```
|
|
|
|
### Nginx conf por cliente (`/etc/nginx/conf.d/castillo.conf`)
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name castillo.rvconsultas.com;
|
|
|
|
# Certbot agrega automáticamente el bloque SSL al correr certbot --nginx
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8201;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_cache_bypass $http_upgrade;
|
|
proxy_read_timeout 300s; # importante para WebSocket
|
|
proxy_send_timeout 300s;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 13. Flujo completo de una venta
|
|
|
|
Este es el flujo técnico detallado de lo que pasa cuando el vendedor escribe en el chat:
|
|
|
|
```
|
|
1. Vendedor escribe "tenés birome bic azul?" en el chat
|
|
2. JS del frontend: WebSocket.send({ msg: "tenés birome bic azul?", session_id: "abc" })
|
|
3. Backend FastAPI recibe el mensaje en el WebSocket handler
|
|
4. Backend envía { typing: true } al frontend → aparece indicador "escribiendo..."
|
|
5. Backend pasa el mensaje a PicoClaw con:
|
|
- El historial de la conversación (hasta 20 turnos)
|
|
- El system prompt del negocio
|
|
- Las herramientas disponibles
|
|
6. PicoClaw llama a Claude API con todo lo anterior
|
|
7. Claude decide llamar la herramienta buscar_stock con { query: "birome bic azul" }
|
|
8. PicoClaw hace GET http://localhost:8000/stock/search?q=birome+bic+azul
|
|
9. FastAPI busca en SQLite: encuentra "Birome Bic Cristal", precio $850, stock 23
|
|
10. FastAPI devuelve el JSON de resultados a PicoClaw
|
|
11. PicoClaw pasa el resultado a Claude
|
|
12. Claude genera la respuesta final: "Sí, tenemos Bic Cristal azul a $850. Quedan 23. ¿Se vendió?"
|
|
13. Backend envía la respuesta al frontend via WebSocket: { msg: "Sí, tenemos...", typing: false }
|
|
14. Frontend muestra el mensaje del bot en el chat
|
|
|
|
--- (segunda vuelta) ---
|
|
|
|
15. Vendedor escribe "sí, 2"
|
|
16. PicoClaw (con el historial que incluye el producto encontrado) llama confirmar_venta
|
|
con { producto_id: 42, cantidad: 2, precio_vendido: 850 }
|
|
17. FastAPI:
|
|
- Verifica: producto 42 existe, activo, stock 23 >= 2 ✓
|
|
- INSERT INTO ventas (producto_id, cantidad, precio_vendido, ...) VALUES (42, 2, 850, ...)
|
|
- UPDATE productos SET stock = stock - 2, updated_at = now() WHERE id = 42
|
|
- Devuelve: { venta_id: 891, stock_nuevo: 21, total: 1700 }
|
|
18. Claude genera confirmación: "¡Listo! 2 Bic Cristal azul a $850 c/u = $1.700. Stock actualizado: quedan 21."
|
|
19. Frontend muestra la confirmación
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Modelo de negocio y precios
|
|
|
|
### Planes
|
|
|
|
| Plan | Precio | Límites |
|
|
|------|--------|---------|
|
|
| Starter | $15 USD/mes | 1 usuario vendedor, hasta 500 productos |
|
|
| Pro | $30 USD/mes | 3 usuarios, productos ilimitados, estadísticas |
|
|
| Red | $45 USD/mes | Todo Pro + tendencias zonales, red de PyMEs |
|
|
|
|
### Costos operativos estimados (por cliente activo)
|
|
|
|
| Item | Costo mensual |
|
|
|------|--------------|
|
|
| Anthropic API (Claude) | ~$1-2 USD (estimado 500 consultas/mes) |
|
|
| VPS (prorrateado entre clientes) | ~$0.15 USD |
|
|
| **Total costo** | **~$2 USD/cliente** |
|
|
|
|
**Margen bruto estimado (plan Starter):** $15 - $2 = $13 USD por cliente
|
|
|
|
---
|
|
|
|
## 15. Roadmap de implementación
|
|
|
|
### Fase 1 — MVP funcional (semanas 1-4)
|
|
**Objetivo:** un vendedor real puede atender un cliente en menos de 2 minutos.
|
|
|
|
- [ ] Estructura de carpetas y Docker Compose template
|
|
- [ ] Backend FastAPI con endpoints: `/stock/search`, `/venta/confirmar`, `/health`
|
|
- [ ] WebSocket de chat (`/chat/ws/{session_id}`)
|
|
- [ ] Integración PicoClaw + Claude API con herramientas reales
|
|
- [ ] Parser de Excel para importar stock inicial
|
|
- [ ] Dashboard mínimo: vista chat (vendedor) + upload Excel (dueño)
|
|
- [ ] Nginx config + SSL en subdominio de prueba
|
|
- [ ] Primer cliente piloto real usando el sistema
|
|
|
|
**Criterio de éxito:** el vendedor completa una consulta completa (buscar → confirmar → stock descontado) en menos de 2 minutos sin entrenamiento previo.
|
|
|
|
### Fase 2 — Dashboard completo (mes 2)
|
|
- [ ] Estadísticas de ventas con gráficos
|
|
- [ ] Sistema de promociones completo
|
|
- [ ] Armado de combos con filtro por edad y género
|
|
- [ ] Alertas de stock mínimo
|
|
- [ ] Vista admin completa
|
|
- [ ] Interfaz responsive para tablet y móvil
|
|
|
|
### Fase 3 — Red de PyMEs (mes 3+)
|
|
- [ ] Módulo opt-in de datos compartidos
|
|
- [ ] Panel de tendencias zonales
|
|
- [ ] API para distribuidores
|
|
|
|
---
|
|
|
|
## 16. Reglas de desarrollo obligatorias
|
|
|
|
Estas reglas son **no negociables**. Si algo entra en conflicto con estas reglas, aplicar la regla.
|
|
|
|
### Seguridad
|
|
1. **La API key de Anthropic NUNCA se hardcodea.** Siempre desde variable de entorno.
|
|
2. **Nunca logear** la contraseña sudo, la API key ni las contraseñas de admin.
|
|
3. **Cada cliente tiene su propio contenedor y su propia DB.** Nunca compartir conexión de DB entre clientes.
|
|
4. **Todas las rutas de admin** deben requerir autenticación. Nunca dejar una ruta admin sin protección.
|
|
|
|
### Datos
|
|
5. **El campo `precio_vendido` en ventas** es siempre el precio real de la venta, no una FK al precio actual del producto.
|
|
6. **Nunca hacer DELETE en la tabla productos.** Usar `activo = 0` para desactivar.
|
|
7. **Todos los timestamps** en formato ISO 8601 UTC: `datetime('now')` en SQLite.
|
|
|
|
### Performance
|
|
8. **El endpoint `/stock/search`** debe responder en menos de 200ms. Si tarda más, optimizar los índices.
|
|
9. **El WebSocket** no debe bloquear. Usar `asyncio` correctamente. Nunca llamadas síncronas dentro de handlers async.
|
|
|
|
### Código
|
|
10. **Usar tipado estricto de Python** con Pydantic para todos los bodies de request y response.
|
|
11. **Nunca usar `*` en imports** (no `from fastapi import *`).
|
|
12. **Cada endpoint** debe tener docstring con descripción, parámetros y qué devuelve.
|
|
13. **Los errores de validación** devuelven siempre JSON con el campo `detail` descriptivo en español.
|
|
14. **Tests:** al menos un test de integración por endpoint crítico (`/stock/search`, `/venta/confirmar`).
|
|
|
|
### UX
|
|
15. **Los mensajes de error** que ve el vendedor deben estar en español coloquial, nunca mostrar stack traces.
|
|
16. **El chat** debe mostrar el indicador de "escribiendo..." mientras el bot procesa. Nunca dejar al vendedor mirando una pantalla en blanco.
|
|
|
|
---
|
|
|
|
*Fin del documento. Versión 1.0 — PymesBot Project Spec*
|