Skip to content

Feat/llm chatbot frontend#3

Merged
echeguate merged 21 commits into
mainfrom
feat/llm-chatbot-frontend
May 26, 2026
Merged

Feat/llm chatbot frontend#3
echeguate merged 21 commits into
mainfrom
feat/llm-chatbot-frontend

Conversation

@juandmg020407
Copy link
Copy Markdown
Collaborator

Resumen

Añade el frontend conversacional del proyecto (web/) como segundo componente coexistente con el backend Python de optimización ya existente. La filosofía es que el LLM local sea la interfaz principal de operación.

El backend Python con OR-Tools, Streamlit, etc. no se toca. Ambos componentes pueden ejecutarse independientemente; la integración HTTP entre ellos queda como evolución natural (descrita en el roadmap).

Qué incluye

  • Stack: Next.js 14 (App Router) + TypeScript + Tailwind + shadcn/ui · Prisma + SQLite · Leaflet + OSRM público · Ollama llama3.1:8b local · JWT en cookie httpOnly.
  • 5 pantallas: /login, /orders, /chat, /routes, /routes/[id].
  • Chatbot con 13 tools (consultar/modificar pedidos, sugerir rutas, asignar conductor, reportar incidencias, reoptimizar tras avería).
  • Parser tolerante para cuando llama3.1:8b emite tool calls como JSON inline en lugar del campo estructurado (fallo conocido del modelo).
  • Privacidad por diseño: los datos de cliente nunca salen de la máquina del usuario.
  • Demo seed: 47 pedidos en 14 barrios de Alicante con coords reales pre-geocodificadas.

Estructura de commits

13 commits granulares siguiendo Conventional Commits:

  1. chore: añadir CONTRIBUTING y CODE_OF_CONDUCT
  2. chore: extender .gitignore con reglas para Node, Next.js y SQLite
  3. feat(web): scaffold Next.js 14 con TypeScript, Tailwind y shadcn/ui
  4. feat(web): añadir esquema Prisma con SQLite y seed de 47 pedidos en Alicante
  5. feat(web): autenticación JWT en cookie httpOnly y pantalla de login
  6. feat(web): clientes OSRM y Nominatim con caché + helpers de formato
  7. feat(web): API routes de pedidos, vehículos, usuarios e incidencias
  8. feat(web): pantalla de pedidos con tabla, filtros y creación con geocoding
  9. feat(web): optimizador VRP con 3 opciones por sector y reschedule por averías
  10. feat(web): cliente Ollama, sistema de tools del chatbot y runner
  11. feat(web): UI del chatbot con sugerencias, burbujas y cards de tools
  12. feat(web): pantalla de mapa Leaflet con polyline real y panel de paradas
  13. docs: ARCHITECTURE, ROADMAP y README ampliado con frontend web/

Cómo probarlo

Requisitos: Node 20+, Ollama instalado, ~5GB libres.

ollama pull llama3.1:8b
cd web
cp .env.example .env
npm install
npx prisma migrate dev
npm run db:seed
npm run dev

Abre http://localhost:3000 e inicia con admin / admin123.

Guion de demo (5 min):

  1. /orders → tabla con 47 pedidos de Alicante.
  2. /chat"Sugiere rutas para hoy" → 3 opciones (Centro, Playa, Completa).
  3. "Asigna la opción B a Juan" → crea RT-2026-XX-XX-A.
  4. /routes/[id] → mapa Leaflet con polyline real por calles + 6-10 marcadores numerados.
  5. Volver al chat: "Se me ha averiado la furgo, 60 minutos" → el LLM reoptimiza y difiere los pedidos que no caben a mañana. ⭐

Decisiones técnicas relevantes

  • Sin OR-Tools en el frontend (TSP simple vía OSRM /trip). Para casos VRP con time windows estrictas o >15 paradas, el roadmap contempla llamar al backend Python como microservicio.
  • Leaflet con react-leaflet@4.2.1 (no v5) porque v5 requiere React 19 y aquí va con React 18.
  • Prisma 6 (no 7) por estabilidad. El v7 introduce sintaxis nueva con prisma.config.ts que evitamos.
  • bcrypt rounds=8 en seed para velocidad. Producción debe subirlo a 12+ (documentado en README).

Notas para revisores

  • El backend Python actual sigue intacto. Esta rama no toca app/, data/, optimizar_rutas.py, requirements.txt, ni los scripts en skills/.
  • Los archivos CONTRIBUTING.md y CODE_OF_CONDUCT.md se añaden en raíz y cubren contribuciones a ambos componentes.
  • La documentación nueva está en docs/ARCHITECTURE.md y docs/ROADMAP.md.
  • Cualquier sugerencia, especialmente sobre la integración Python ↔ web/ futura, es bienvenida.

📸 Capturas de pantalla

Mapa de ruta optimizada

Ruta RT-2026-05-25-A planificada por el chatbot y asignada a María (furgo 5678-DEF). Polyline real por calles de Alicante calculada vía OSRM, 10 marcadores numerados en orden óptimo del TSP, depósito como icono cuadrado oscuro. Panel lateral con ETAs y franjas horarias del cliente.

image

Centro de comandos (chatbot)

El chatbot LLM local actúa como UI principal del sistema. Identificado como OpenRoute Assistant · llama3.1:8b · tool calling local. Sugerencias predefinidas para arrancar conversación en español natural.

image

Sugerencia de Rutas

El administrador solicita rutas para hoy en lenguaje natural y el sistema reorganiza la ruta automáticamente: reoptimiza las paradas pendientes con OSRM, mueve a mañana las que ya no caben en su franja, registra una incidencia y comunica las nuevas ETAs. La tarjeta plegable ⚙️ reschedule_route ✓ muestra el JSON del resultado para auditoría y transparencia.

image

Base de datos de pedidos

47 pedidos sembrados en 14 barrios reales de Alicante (Centro, Playa San Juan, Albufereta, Vistahermosa, San Blas, Carolinas, Benalúa, Garbinet, Babel, Florida, Pla). Tabla con filtro por estado, búsqueda libre, badges semánticos y formato es-ES de fechas.

image

Listado de rutas planificadas

Vista resumen con métricas (kms, duración, % entregadas). Las nuevas rutas se crean desde el chatbot, esta pantalla es solo lectura.

image

cc: @echeguate @giulianpeter

Documentos meta del proyecto para facilitar la participación inclusiva
y la reutilización por terceros (criterio de código abierto del hackathon).

- CONTRIBUTING.md: flujo de PRs, conventional commits, áreas donde se busca
  ayuda, distinción entre backend Python y frontend web/.
- CODE_OF_CONDUCT.md: Contributor Covenant 2.1 en español.
El frontend web/ que llega en commits posteriores requiere excluir
node_modules, .next, .env y archivos de DB local.

Las reglas Python preexistentes se mantienen intactas, solo se anclan
con / inicial las que apuntan a carpetas del root (build/, lib/, dist/,
etc.) para evitar colisiones con subdirectorios como web/src/lib/.
Estructura inicial del frontend conversacional que actuará como centro de
comandos del sistema. Stack base:

- Next.js 14 (App Router) + TypeScript estricto
- Tailwind CSS + shadcn/ui (componentes accesibles, estilo "new-york")
- Layout raíz en español con fuente system + Toaster (sonner)
- Logo y favicon corporativos en color verde #1a531a

Incluye los primitivos shadcn que se usan en pantallas siguientes:
Button, Input, Label, Card, Dialog, Select, Table, Textarea, Badge, Sonner.

El page.tsx raíz redirige a /orders si hay sesión o /login en caso contrario
(la lógica de sesión llega en el commit de auth).
…licante

Modelo de datos canónico para el dominio de reparto en última milla. Tablas:

- User (ADMIN | DRIVER), Vehicle, Customer
- Order: pedido con franja horaria, dirección, lat/lng cacheados, estado
- Route + RouteStop: rutas planificadas con polyline OSRM persistida y
  paradas ordenadas por sequence con ETA planificada
- Incident: averías, paquetes no entregables, tráfico, etc.
- ChatSession + ChatMessage: auditoría completa del LLM con tool calls
  serializados (permite reproducir y debuggear conversaciones)
- GeocodeCache: caché persistente de Nominatim (rate limit 1 req/s)

Las lat/lng de cada Order y la polyline de cada Route se almacenan
cacheadas para evitar re-llamar a APIs externas al refrescar páginas.

Seed:
- 2 admins + 3 conductores con bcrypt (admin/admin123, juan/juan123, ...)
- 3 furgonetas con capacidades distintas
- 30 clientes con nombres reales
- 47 pedidos repartidos por 14 barrios de Alicante con coords reales
  pre-computadas (Centro, Playa San Juan, Albufereta, Vistahermosa,
  San Blas, Carolinas, Benalúa, Garbinet, Babel, Florida, Pla, Altozano).
- Mezcla de estados realistas: 30 PENDING hoy, 5 IN_TRANSIT, 5 DISPATCHED,
  10 DELIVERED ayer, 2 FAILED ayer.

Las direcciones reales pre-geocodificadas evitan martillear Nominatim
durante la demo y garantizan reproducibilidad.

.env.example documenta todas las variables sin filtrar secretos reales.
Sistema de sesión simple sin OAuth, suficiente para piloto interno:

- lib/auth.ts: sign/verify JWT, cookies httpOnly + sameSite=lax, bcrypt para
  passwords, helper getSession() lee la sesión en server components.
- middleware.ts: protege /dashboard/* y /api/*, redirige a /login si no hay
  sesión. Excluye /api/auth/login para evitar bucle.
- API: /api/auth/login (zod-validated), /api/auth/logout, /api/auth/me.
- /login: pantalla con branding verde corporativo, gradient #0d2f0d→#1a531a,
  logo SVG y muestra los usuarios demo para facilitar la prueba.
- Sidebar compartida con navegación entre Pedidos / Chatbot / Rutas y
  botón de cerrar sesión.
- Layout del grupo (dashboard) con auth guard server-side.

Seguridad: bcrypt rounds bajos (8) para velocidad de seed - documentar
en README que en producción debe subirse a 12+.
Wrappers ligeros sobre los dos servicios públicos abiertos que el sistema
consume para routing y geocoding:

- lib/osrm.ts: clientes para los endpoints /route y /trip de
  router.project-osrm.org. /trip resuelve el TSP devolviendo el orden
  óptimo de paradas. Ambos cachean en memoria por hash de coordenadas
  para minimizar la presión sobre el servicio público (gratuito) y
  acelerar respuestas durante la demo. Decodifica polylines polyline6.
- lib/nominatim.ts: geocoding con throttle de 1 req/segundo según la
  política de Nominatim, y caché persistente en la tabla GeocodeCache
  para que cada dirección sólo se pida una vez. Devuelve null si falla
  para que el llamante decida (evita romper el flujo si la red falla).
- lib/format.ts: helpers de formato es-ES (fechas, horas, distancias,
  duraciones, etiquetas legibles de estados de Order/Route/Stop).

Estos clientes son la única superficie del sistema que habla con el
exterior, lo que facilita auditarlos y eventualmente cambiarlos
(p. ej. auto-hospedar OSRM/Nominatim para no depender del servicio
público).
Endpoints REST en Next.js App Router con validación zod en todos los
inputs y guard de sesión vía getSession(). Operaciones:

- /api/orders
  - GET: lista con filtros por status, date (YYYY-MM-DD) y q (búsqueda
    libre en code, street y customer.name). Incluye customer y routeStop.
  - POST: crea pedido + customer si no existe + dispara geocoding contra
    Nominatim. El código se autogenera ORD-YYYY-NNNNN.
- /api/orders/[id] - GET / PATCH / DELETE (solo ADMIN). PATCH re-geocodifica
  si cambia dirección y trata el id como id o como code.
- /api/vehicles - GET con filtro ?available=true.
- /api/users - GET con filtro ?role= (útil para listar conductores).
- /api/incidents - GET / POST. La incidencia queda asociada a pedido y/o
  ruta y registra al usuario que la reportó.

Todos los endpoints devuelven 401 sin sesión, 400 si zod falla, y 404 si
el recurso no existe. Mensajes de error en español.
…oding

Primera pantalla de la app (/orders), enfocada al despachador. Componentes:

- OrdersTable: tabla shadcn con búsqueda libre (código, cliente, calle),
  filtro por estado (PENDING, DISPATCHED, IN_TRANSIT, DELIVERED, FAILED,
  RESCHEDULED), contador de resultados y formato es-ES de fechas/horas.
  Marca visualmente si el pedido tiene coordenadas geocodificadas o no.
- OrderStatusBadge: badge con paleta diferenciada por estado.
- OrderFormDialog: diálogo de creación con campos cliente / dirección /
  franja horaria / peso / notas. Si la dirección no existía en
  GeocodeCache se geocodifica vía Nominatim en el momento del POST y se
  muestra toast con confirmación de éxito + indicador de si se geocodificó.
- page.tsx: server component que carga los pedidos con Prisma desde DB y
  los pasa serializados al componente cliente. force-dynamic para reflejar
  cambios en tiempo real al volver de otras pantallas.

La tabla está limitada a 500 pedidos por carga (suficiente para PYMEs)
sin paginación todavía: optimización para fases posteriores cuando la
escala lo requiera.
… averías

Núcleo lógico de la optimización de rutas. Combina OSRM /trip (resuelve TSP)
con filtros de sector y restricciones de ventana horaria para producir
soluciones útiles sin necesidad de OR-Tools embebido.

lib/optimize.ts:
- optimizeOption(date, sector, ...): selecciona pedidos PENDING/DISPATCHED
  del día filtrados por sector geográfico, los manda a OSRM con depósito
  como source, y construye paradas con ETA acumulada (servicio + travel).
  Marca withinWindow=false si la ETA cae fuera de la franja del cliente.
- suggestRoutes(date): genera hasta 3 opciones legibles
    A. Ruta Centro (Centro, Carolinas, Benalúa, Florida)
    B. Ruta Playa San Juan (Playa San Juan, Albufereta)
    C. Ruta Completa (mezcla de sectores, truncada a 14 stops)
  La clasificación es por bounding box de lat/lng - suficiente para
  Alicante; en producción usar un GeoJSON de barrios.
- rescheduleRoute(routeId, delayMinutes): RE-OPTIMIZA tras avería.
    1. Identifica paradas pendientes (no DELIVERED/FAILED).
    2. Punto de partida = última parada entregada o depósito si ninguna.
    3. Suma el delay al tiempo actual y vuelve a llamar a OSRM /trip.
    4. Para cada parada, si la nueva ETA supera windowEnd se mueve a la
       lista de DIFERIDAS (se reprograman a mañana).
  Esta función es la que materializa el "wow" de la demo: el chatbot la
  llama con "se me ha averiado la furgo, X minutos" y todo se reorganiza.

API:
- POST /api/optimize: proxy fino sobre suggestRoutes (futuro: delegar a
  microservicio Python con OR-Tools cuando haya time windows estrictas).
- /api/routes: GET por fecha/conductor/status, POST que persiste una
  opción aceptada (crea Route + RouteStops, llama a OSRM /route para
  guardar la polyline final, marca furgoneta no disponible, pone Orders
  en DISPATCHED).
- /api/routes/[id]: GET con stops y conductor.

Pantalla /routes: listado en tarjetas con resumen visual (entregadas/total,
kms, duración). Las nuevas rutas se crean desde el chatbot.
El centro de comandos del sistema: chatbot conversacional en español que
interactúa con el resto de la app vía tool calling sobre un LLM local.
Privacidad por diseño - los datos de clientes no salen de la máquina.

lib/ollama-client.ts:
- Wrapper sobre POST /api/chat de Ollama (puerto 11434).
- Configurado con llama3.1:8b por defecto, keep_alive=30m para mantener
  el modelo en memoria entre turnos, num_ctx=4096, temperatura baja (0.2).
- Soporta el campo tools en formato OpenAI-compatible.

lib/chat/tools.ts: define 13 herramientas con JSONSchema que el LLM puede
invocar:
  current_time, list_orders, get_order, update_order,
  list_vehicles, list_drivers, suggest_routes, assign_route,
  list_routes, get_route, report_incident, reschedule_route,
  mark_stop_delivered.

lib/chat/system-prompt.ts: prompt en español con identidad
"OpenRoute Assistant" y reglas de conducta:
  - Llamar current_time antes de filtrar por "hoy".
  - Confirmar antes de mutaciones.
  - Comunicar claramente qué se diferió a mañana tras averías.

lib/chat/tool-handlers.ts: implementación de cada tool sobre Prisma +
helpers de optimize. Devuelve datos condensados al LLM (no documentos
completos) para mantener el prompt corto. Mantiene un mapa
lastSuggestions por sessionId para que assign_route pueda recuperar las
opciones devueltas por suggest_routes (cache en memoria del proceso).

lib/chat/runner.ts: loop tool-calling con tope de 5 iteraciones. Carga
historial de la sesión (últimos 24 mensajes), envía a Ollama, ejecuta
tool calls, persiste cada paso (ChatMessage role=user/assistant/tool con
toolCalls JSON y toolName). Si Ollama falla devuelve el error como
mensaje del asistente sin romper.

lib/chat/parse-tool-calls.ts: parser tolerante para cuando llama3.1:8b
emite tool calls como JSON inline en el texto en lugar de usar el campo
estructurado tool_calls (fallo conocido del modelo). Extrae los JSON
con escaneo brace-balanced y los convierte en ToolCall sintéticos.
Repara también referencias a variables sin comillas como current_time.

API /api/chat:
- POST: ejecuta el runner. Crea ChatSession nueva si no se pasa
  sessionId existente. Devuelve los nuevos mensajes + uiHints para que
  la UI muestre links a rutas creadas.
- GET sin sessionId: lista las últimas 10 sesiones del usuario.
- GET con sessionId: historial completo de mensajes.

El sistema es completamente offline (con Ollama local + OSRM cacheado),
preservando privacidad de los datos del cliente.
Pantalla /chat - el centro de comandos del sistema desde la perspectiva
del usuario.

components/chat/ChatWindow.tsx (client component):
- Estado local de mensajes que se sincroniza con la sesión persistida.
- Auto-scroll al fondo en cada respuesta nueva.
- Pantalla vacía: 4 sugerencias para arrancar conversación
    "Necesito rutas para hoy"
    "¿Qué pedidos hay pendientes?"
    "¿Cuántas rutas se han hecho hoy?"
    "Se me ha averiado la furgo, 45 minutos"
- Input: Textarea con Enter para enviar, Shift+Enter para nueva línea.
- Indicador "Pensando..." con spinner mientras el backend ejecuta el
  runner (puede tardar 5-20s con llama3.1:8b la primera vez).
- Si una respuesta crea/reasigna una ruta, aparece atajo "Ver ruta XX →"
  en la cabecera enlazando a /routes.
- Toast con error si la red o el servidor fallan.

components/chat/MessageBubble.tsx:
- Burbuja distinguida por rol (user / assistant / tool).
- Mensajes tool plegables con <details> mostrando JSON formateado y
  marca visual ✓ / ✗ según si la herramienta tuvo éxito. Esto da
  transparencia al usuario sobre qué está haciendo el LLM (criterio
  de IA explicable / responsable).

El chatbot es accesible solo a usuarios autenticados (protección por
middleware) y cada conversación queda auditada en la DB.
Visualización end-to-end de una ruta planificada. Pantalla /routes/[id]
con dos paneles sincronizados:

components/map/RouteMap.tsx (client-only, dynamic import sin SSR):
- MapContainer Leaflet + tiles OpenStreetMap (atribución correcta).
- Polyline decodificada desde el formato polyline6 que devuelve OSRM,
  pintada en verde corporativo #1a531a con weight=5.
- Marcadores numerados (1, 2, 3...) usando divIcon con CSS custom
  (gota invertida con número dentro). Color por StopStatus:
    azul = PENDING, verde = DELIVERED, rojo = FAILED/SKIPPED, ámbar = SELECTED.
- Marcador especial cuadrado para el depósito con icono 🏭.
- FitBounds automático en montaje + FlyTo cuando se selecciona una
  parada desde el panel lateral.

components/routes/RouteDetailClient.tsx:
- Cabecera con código de ruta, conductor, furgoneta, distancia, duración,
  contador entregadas/total y badge de estado.
- Panel lateral derecho con lista de paradas en orden óptimo: secuencia,
  cliente, dirección, código de pedido, ETA planificada, ventana del
  cliente, notas. Botón "Marcar entregada" en las paradas PENDING.
- Selección sincronizada: clic en parada centra el mapa con flyTo,
  clic en marcador selecciona la parada en el panel.
- Sugerencia inline para usar el chatbot si surge una incidencia.

API /api/routes/[id]/stops/[stopId] PATCH:
- Marca la parada como ARRIVED / DELIVERED / FAILED / SKIPPED.
- Cascada al Order asociado (DELIVERED → status DELIVERED, etc.).

Leaflet se importa dinámicamente con ssr:false para evitar errores de
window en Server Components, y su CSS se incluye desde globals.css.
Documentación de proyecto que refleja la nueva estructura coexistente
backend Python + frontend Next.js. Pensada para que cualquier tercero
pueda entender, ejecutar y extender el sistema (criterio de "diseño
para la reutilización" del scoring de código abierto).

README.md (ampliado, conservando el contenido original del equipo):
- Sección "Dos componentes complementarios" que aclara la separación.
- Instalación dividida en backend Python (Streamlit + OR-Tools) y
  frontend web/ (Next.js + Ollama + OSRM + Leaflet).
- Tabla de usuarios demo del frontend.
- Guión de demo end-to-end de 5 minutos del frontend.
- Hoja de ruta resumida + enlace a ROADMAP.md.
- Sección de contribuciones + enlace a CONTRIBUTING.md.
- "Built with": agradecimientos a todas las dependencias open source
  con sus licencias, separadas por componente.
- Badge de licencia actualizado a Apache 2.0.
- Nombre unificado a "OpenRoute" en lugar de "OpenRoutePyME".

docs/ARCHITECTURE.md (nuevo):
- Diagrama ASCII de cada componente.
- Decisiones técnicas con su contexto: por qué Ollama local, por qué
  SQLite, por qué Next.js 14 App Router, etc.
- Modelo de datos completo con explicaciones.
- Estructura de carpetas de web/ con propósito de cada archivo.
- Documentación del sistema de tool calling y los 13 tools del chatbot.
- Flujo end-to-end del "wow" de la demo (auto-gestión de averías).
- Sección "Decisiones que no escalan" con la deuda técnica conocida.
- Hoja de ruta de integración Python ↔ web/.

docs/ROADMAP.md (nuevo):
- Estado actual separado por componente.
- Corto plazo (1-2 meses): integración, tests, CI/CD, Docker, Postgres.
- Medio plazo (3-6 meses): ficha técnica conductor, app móvil PWA,
  notificaciones cliente, vista despachador en tiempo real.
- Largo plazo (6-12 meses): multi-tenant, integración ERPs/ecommerce, i18n.
- Mejoras transversales: tests, accesibilidad, docs, observabilidad.
…ense

Dos cambios necesarios para que el build de producción pase limpio (sin
warnings de ESLint ni errores de prerender):

- RouteMap.tsx: eliminar import useRef no usado.
- tool-handlers.ts: eliminar import osrmRoute no usado.
- optimize.ts: eliminar import osrmRoute no usado.
- login/page.tsx: extraer el componente que usa useSearchParams a un
  child LoginForm y envolverlo en <Suspense>. Sin esto Next.js 14 falla
  en build con "useSearchParams() should be wrapped in a suspense
  boundary" porque /login se prerendera estáticamente.

Verificado en local:
- npm run lint  →  ✓ No ESLint warnings or errors
- npx tsc --noEmit  →  sin errores
- npm run build  →  ✓ Compiled successfully

Este fix prepara el terreno para el workflow de CI del siguiente commit.
…rontend

Workflow .github/workflows/web-ci.yml que se dispara en:
- Cada Pull Request que toque web/** o el propio workflow.
- Cada push a main que afecte a web/**.

El job lint-typecheck-build corre en ubuntu-latest e incluye:
  1. Setup de Node.js 20 con caché de npm vía package-lock.
  2. npm ci (instalación reproducible).
  3. npx prisma generate (los tipos del cliente Prisma son necesarios
     para el type-check).
  4. npm run lint (ESLint reglas Next.js).
  5. npx tsc --noEmit (validación de tipos sin emitir JS).
  6. npm run build (build de producción de Next.js).

El build recibe envs dummy via env: para que no falle por falta de
DATABASE_URL/JWT_SECRET. En producción real estos vienen de secrets.

Beneficios del CI para el proyecto:
- Bloquea PRs que rompan el build antes del merge.
- Visibilidad de calidad: badge verde/rojo junto a cada commit del PR.
- Refuerza la "seguridad por diseño" y "diseño para la reutilización"
  del criterio de código abierto del hackathon (25% del scoring).

Tiempo estimado del workflow: ~3 minutos.
Coste: gratis en repos públicos.

El backend Python tendrá su propio workflow en una iteración posterior
(py-ci.yml con ruff + pytest), una vez la suite del backend madure.
…onfusa

Los componentes Dialog y Select de shadcn usaban variables CSS de fondo
(bg-background, bg-popover, bg-accent) que dependían de los tokens oklch
de globals.css. En el navegador del usuario no se renderizaban bien y los
modales/dropdowns aparecían translúcidos, dejando ver las tablas y
listados de detrás superpuestos al contenido (texto sobre texto).

Cambios:

- Dialog overlay: bg-black/80 → bg-slate-900/70 con backdrop-blur-sm.
  Más uniforme y con efecto blur sutil que mejora la jerarquía visual.
- Dialog content: bg-background → bg-white sólido (dark:bg-slate-900).
  Borde explícito border-slate-200 y shadow-2xl en lugar de shadow-lg
  para que destaque más sobre el overlay.
- Select content: bg-popover → bg-white sólido (dark:bg-slate-900) con
  border-slate-200 y shadow-lg.
- Select item: focus:bg-accent → hover:bg-slate-100 focus:bg-slate-100.
  Ahora el hover funciona en lugar de solo el focus.

Reportado por el usuario tras prueba del módulo "Nuevo pedido" y filtro
de estados en /orders.
El modelo llama3.1:8b envía con frecuencia los argumentos numéricos como
strings ("20" en lugar de 20), provocando un error de Prisma al intentar
guardar en el campo Int de Incident.durationMin o al calcular el offset
del reschedule. El bot lo manifestaba como una respuesta confusa al
usuario en lugar de actuar.

Cambios en tool-handlers.ts:

- Nuevo helper toNumber(value) que acepta number, string numérico, o
  incluso strings con unidades ("20 minutos" → 20) y devuelve undefined
  si no es coercible. Mantiene null seguro frente a Prisma.
- suggest_routes: maxStops → toNumber(args.maxStops) ?? 10.
- report_incident: durationMin → toNumber(args.durationMin) ?? null.
- reschedule_route: delayMinutes → toNumber + validación explícita con
  mensaje de error claro ("debe ser un número de minutos") si falla.

Cambios en system-prompt.ts:

- Nueva sección "FORMATO DE ARGUMENTOS (IMPORTANTE)" que instruye al
  modelo a enviar los numéricos como números, no strings, con ejemplo
  correcto vs incorrecto explícito.
- Indica también qué campos son strings (códigos, fechas) y cuáles son
  numéricos, para reducir alucinaciones.

Esto debería resolver el caso reportado por el usuario donde "20
minutos de avería" provocaba un error en Prisma sobre Int|Null.
…ipal

Antes, el layout del dashboard usaba min-h-screen y el main con
overflow-auto, lo que en /orders y /routes hacía que el scroll de la
tabla/lista arrastrase a toda la página, haciendo que el sidebar
"subiera" cuando el usuario bajaba en la tabla.

Cambios:
- DashboardLayout: min-h-screen → h-screen overflow-hidden en el div
  contenedor. Así el viewport del shell queda fijo y el desbordamiento
  vertical lo absorbe únicamente el <main>.
- <main>: overflow-auto → overflow-y-auto overflow-x-hidden. Sólo el
  área de contenido scrollea verticalmente, sin arrastrar el sidebar.
  El overflow-x-hidden evita scroll horizontal accidental por
  componentes anchos.
- Sidebar: añadir h-full para ocupar toda la altura del shell de forma
  consistente (importante cuando hay poco contenido en una pantalla
  como /routes vacía).

Resultado: el sidebar queda visualmente "anclado" a la izquierda en
todas las pantallas, sin saltar al hacer scroll.
Reemplaza el login básico (card centrada sobre gradient verde) por un
layout de dos columnas tipo SaaS moderno, alineado con estándares de
producto B2B.

Lado izquierdo (oculto en móvil, 50% en lg+):
- Gradient diagonal con los tonos corporativos (#0d2f0d → #1a531a → #134013).
- Patrón decorativo sutil de puntos con baja opacidad.
- Logo blanco (nueva variante public/logo-white.svg) sobre fondo
  semitransparente con backdrop blur, en lugar del logo verde sobre
  caja blanca que se veía descontextualizado.
- Tagline grande "Tu flota, optimizada con IA local" + sub-descripción
  del producto.
- Lista de 3 features clave del sistema (chatbot LLM local,
  optimización VRP, auto-gestión averías) con iconos lucide.
- Footer pequeño con la atribución al hackathon.

Lado derecho (formulario):
- Fondo blanco limpio.
- Heading "Bienvenido de vuelta" + subtítulo claro.
- Inputs con altura h-11 (más cómodos al click) e iconos User/Lock
  embebidos a la izquierda.
- Botón "Entrar" más grande y con sombra.
- Sección de usuarios demo colapsable: por defecto cerrada para no
  abrumar; al expandir muestra 5 cards (admin, despacho, juan, maria,
  carlos) clickables que autorellenan el formulario.
- Mensaje de error con icono AlertCircle dentro de un panel rojo claro
  con borde, en lugar del texto rojo plano de antes.

Responsive: en pantallas <lg solo se ve el formulario, con una marca
pequeña de OpenRoute en la esquina superior izquierda.

Mantiene el <Suspense> sobre el componente que usa useSearchParams para
que el build de producción de Next.js 14 prerendere /login estáticamente
sin errores.

Reportado por el usuario tras revisar la pantalla de login.
@echeguate
Copy link
Copy Markdown
Collaborator

A tope !

@echeguate echeguate merged commit 6b27e00 into main May 26, 2026
1 check passed
juandmg020407 added a commit that referenced this pull request May 26, 2026
…IA local

Integra el backend de optimización completo de OpenRoute en main,
sumándose al frontend conversacional del PR #3 para completar el
sistema end-to-end.

CONTENIDO INTEGRADO

Motor VRP (src/):
- optimizer.py — solver dual con patrón Strategy: heurística propia
  (K-Means + Vecino Más Cercano Ponderado) y Google OR-Tools (CVRPTW
  industrial) con fallback automático.
- data_processor.py — limpieza, validación y matrices Haversine
  ajustadas ×1.3 para reflejar el callejero urbano.
- metrics.py — simulador baseline manual con 3 heurísticas humanas
  + cálculo de matriz comparativa de ahorros.
- ai_assistant.py — generador de informes en lenguaje natural con
  Ollama local (mismo modelo que el frontend) + fallback de
  plantillas heurísticas que calculan recomendaciones dinámicamente
  a partir de los datos reales de la flota.
- test_optimizer.py, test_run.py — suite de pruebas unitarias y
  test end-to-end con reporte comparativo.

Datos (data/):
- pedidos_ejemplo.csv — 30 pedidos reales en Elche y Alicante con
  coordenadas precisas, prioridades, pesos y ventanas horarias.
- vehiculos_config.json — flota de 3 furgonetas (eléctrica, diésel,
  apoyo) con capacidades y costes por kilómetro.

Módulos reutilizables (skills/):
- 1_data_cleaning/, 2_eda/, 3_models/ — scripts académicos
  modularizados para reutilización en otros proyectos.

Documentación:
- docs/BACKEND_INTEGRATION.md — guía técnica en 3 pasos para
  consumir el motor desde Streamlit, FastAPI o cualquier app Python.

Dependencias:
- requirements.txt actualizado con numpy, ortools y requests, además
  de las ya existentes (streamlit, pandas, folium, streamlit-folium).

IMPACTO MEDIDO (30 pedidos, 3 furgonetas, baseline manual vs OpenRoute)

  Distancia:  373.51 km → 172.39 km   (-53.8%)
  Coste:      88.39 €   → 44.94 €     (-49.2%)
  CO2:        82.17 kg  → 37.93 kg    (-53.8%)
  Tiempo:     25.12 h   → 18.45 h     (-26.6%)
  Sobrecargas: 3 → 2 (un incidente físico evitado)

NOTAS DEL PROCESO

La rama original feature/openroute-backend se creó con historia git
independiente, lo que impedía a GitHub generar el PR estándar. Para
preservar todos los commits originales con su atribución, se merge
mediante una rama intermedia feat/backend-optimization derivada de
main con --allow-unrelated-histories. Los 8 commits de Samuel
aparecen en el historial con su nombre como autor.

Tres commits adicionales aplicados antes del merge:
- f27fa5f — migración del AIAssistant de Gemini API a Ollama local
  (coherencia con frontend e "IA Responsable y Abierta") + fix de
  plantilla de respaldo (referencia personal eliminada, valores
  hardcodeados sustituidos por cálculo dinámico).
- dd8d34e — README personal del autor movido a la guía técnica
  docs/BACKEND_INTEGRATION.md para evitar conflicto con el README
  del frontend.
- 65dbcbd — eliminados .cloudecode_rules (config personal del IDE
  del autor) y carpeta OpenRoute/ (PDFs de SEDIA con copyright de
  la organización + docs internos del equipo, accesibles aún en el
  historial git).

ESTADO RESULTANTE DEL REPO

main ahora contiene el producto OpenRoute completo:
- Frontend Next.js + chatbot LLM en web/ (PR #3).
- Backend Python VRP + IA local en src/, data/, skills/.
- Documentación unificada en docs/.
- Un único README ampliado en raíz que cubre ambos componentes.

Co-authored-by: Samuel Parra <parrasamuel453@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants