feat: initial commit — HermesMessages SaaS platform
Backend (FastAPI + Python 3.12): - Multi-tenant auth with JWT: login, register, refresh, Meta OAuth - Business & BusinessConfig management - WhatsApp webhook with HMAC signature verification - Bot engine powered by Claude AI - Calendar availability with Redis caching - Reservations CRUD with status management - Dashboard analytics (stats, agenda, peak hours) - Billing & plan management - Admin panel with platform-wide stats - Async bcrypt via asyncio.to_thread - IntegrityError handling for concurrent registration race conditions Frontend (React 18 + Vite + Tailwind CSS): - Multi-step guided registration form with helper text on every field - Login page with show/hide password toggle - Protected routes with AuthContext - Dashboard with stats cards, bar chart, and daily agenda - Reservations list with search, filters, and inline status actions - Calendar with weekly view, slot availability, and date blocking - Config page: business info, schedules, bot personality - Billing page with plan comparison and usage bar Design system: - Bricolage Grotesque + DM Sans typography - Emerald primary palette with semantic color tokens - scale(0.97) button press feedback, ease-out animations - Skeleton loaders, stagger animations, prefers-reduced-motion support - Accessible: aria-labels, visible focus rings, 4.5:1 contrast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
73
backend/app/modules/bot_engine/prompt.py
Normal file
73
backend/app/modules/bot_engine/prompt.py
Normal file
@ -0,0 +1,73 @@
|
||||
from app.modules.bot_engine.schemas import CollectedData, ConversationContext
|
||||
from app.modules.business.models import Business, BusinessConfig
|
||||
from app.modules.calendar.schemas import DayAvailability
|
||||
|
||||
DAYS_ES = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"]
|
||||
|
||||
|
||||
def _format_open_days(open_days: list[int]) -> str:
|
||||
return ", ".join(DAYS_ES[d] for d in sorted(open_days))
|
||||
|
||||
|
||||
def _format_slots(availability: DayAvailability | None) -> str:
|
||||
if not availability or not availability.is_open:
|
||||
return "No hay disponibilidad para esa fecha."
|
||||
available = [s for s in availability.slots if s.available > 0]
|
||||
if not available:
|
||||
return "No quedan slots disponibles para esa fecha."
|
||||
return ", ".join(s.time_start.strftime("%H:%M") for s in available)
|
||||
|
||||
|
||||
def build_system_prompt(
|
||||
business: Business,
|
||||
config: BusinessConfig,
|
||||
availability: DayAvailability | None,
|
||||
context: ConversationContext,
|
||||
) -> str:
|
||||
tone_instruction = (
|
||||
"Usa un tono formal y profesional."
|
||||
if config.tone == "formal"
|
||||
else "Usa un tono amigable y cercano."
|
||||
)
|
||||
|
||||
collected = context.collected_data
|
||||
collected_summary = "\n".join([
|
||||
f"- Nombre: {collected.client_name or 'pendiente'}",
|
||||
f"- Fecha: {collected.date or 'pendiente'}",
|
||||
f"- Hora: {collected.time_start or 'pendiente'}",
|
||||
f"- Personas: {collected.party_size or 'pendiente'}",
|
||||
])
|
||||
|
||||
slots_info = _format_slots(availability)
|
||||
|
||||
return f"""Eres {config.assistant_name}, asistente virtual de {business.name}.
|
||||
{tone_instruction}
|
||||
Responde SIEMPRE en el idioma del cliente.
|
||||
|
||||
HORARIO DEL NEGOCIO:
|
||||
- Días de atención: {_format_open_days(config.open_days or [])}
|
||||
- Horario: {config.open_time.strftime("%H:%M")} a {config.close_time.strftime("%H:%M")}
|
||||
- Duración de cada turno: {config.slot_duration} minutos
|
||||
- Capacidad por turno: {config.max_per_slot} persona(s)
|
||||
|
||||
SLOTS DISPONIBLES PARA LA FECHA SOLICITADA: {slots_info}
|
||||
|
||||
DATOS YA RECOPILADOS DEL CLIENTE:
|
||||
{collected_summary}
|
||||
|
||||
OBJETIVO: Recopilar nombre, fecha, hora y número de personas para crear la reserva.
|
||||
Si ya tienes todos los datos, acción = "create_reservation".
|
||||
Si el cliente quiere cancelar, acción = "cancel".
|
||||
En cualquier otro caso, acción = "collect_more".
|
||||
|
||||
Responde ÚNICAMENTE con JSON válido siguiendo este esquema exacto (sin markdown, sin explicaciones):
|
||||
{{
|
||||
"message": "<texto para enviar al cliente>",
|
||||
"action": "collect_more" | "create_reservation" | "cancel",
|
||||
"collected_data": {{
|
||||
"client_name": null | "<nombre>",
|
||||
"date": null | "<YYYY-MM-DD>",
|
||||
"time_start": null | "<HH:MM>",
|
||||
"party_size": null | <número>
|
||||
}}
|
||||
}}"""
|
||||
Reference in New Issue
Block a user