avances poderosos en ui
Some checks failed
build-and-deploy / filter (push) Successful in 4s
build-and-deploy / build (push) Failing after 19s
build-and-deploy / deploy (push) Has been skipped

This commit is contained in:
2025-05-25 08:21:57 -06:00
parent 55645d0cdd
commit 421ff236ae
40 changed files with 1669 additions and 425 deletions

View File

View File

@@ -1,12 +1,11 @@
import express from 'express';
import { PrismaClient } from './prisma/generated/client/index.js';
import { Decimal } from '@prisma/client/runtime/library.js';
BigInt.prototype.toJSON = function () { return this.toString(); };
Decimal.prototype.toJSON = function () { return this.toString(); };
const prisma = new PrismaClient();
const app = express();
export const app = express();
app.use(express.json());
app.get('/api/test', (req, res) => res.json({ message: 'Hello World' }));

View File

@@ -31,10 +31,11 @@ C:\no guardar\nucleo V3\planilla\agent\respuestas\respuestaNormal.js
C:\no guardar\nucleo V3\planilla\agent\utils\decryptMediaContent.js
C:\no guardar\nucleo V3\planilla\agent\utils\processMessage.js
C:\no guardar\nucleo V3\planilla\agent\utils\saveMedia.js
C:\no guardar\nucleo V3\planilla\api\cron_jobs
C:\no guardar\nucleo V3\planilla\api\dev
C:\no guardar\nucleo V3\planilla\api\prisma
C:\no guardar\nucleo V3\planilla\api\routes
C:\no guardar\nucleo V3\planilla\api\.env
C:\no guardar\nucleo V3\planilla\api\.env.example
C:\no guardar\nucleo V3\planilla\api\Dockerfile
C:\no guardar\nucleo V3\planilla\api\entrypoint.sh
C:\no guardar\nucleo V3\planilla\api\package-lock.json
@@ -42,6 +43,7 @@ C:\no guardar\nucleo V3\planilla\api\package.json
C:\no guardar\nucleo V3\planilla\api\server.js
C:\no guardar\nucleo V3\planilla\api\dev\docker-compose.yml
C:\no guardar\nucleo V3\planilla\api\prisma\generated
C:\no guardar\nucleo V3\planilla\api\prisma\migrations
C:\no guardar\nucleo V3\planilla\api\prisma\schema.prisma
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime
@@ -67,6 +69,10 @@ C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\library.d.t
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\library.js
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\react-native.js
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\wasm.js
C:\no guardar\nucleo V3\planilla\api\prisma\migrations\20250515020056_init
C:\no guardar\nucleo V3\planilla\api\prisma\migrations\migration_lock.toml
C:\no guardar\nucleo V3\planilla\api\prisma\migrations\20250515020056_init\migration.sql
C:\no guardar\nucleo V3\planilla\api\routes\clientesRandom.js
C:\no guardar\nucleo V3\planilla\dev\scripts
C:\no guardar\nucleo V3\planilla\dev\scripts\estructura.ps1
C:\no guardar\nucleo V3\planilla\mcp\Dockerfile
@@ -84,11 +90,53 @@ C:\no guardar\nucleo V3\planilla\ui\vite.config.js
C:\no guardar\nucleo V3\planilla\ui\public\vite.svg
C:\no guardar\nucleo V3\planilla\ui\src\assets
C:\no guardar\nucleo V3\planilla\ui\src\components
C:\no guardar\nucleo V3\planilla\ui\src\layouts
C:\no guardar\nucleo V3\planilla\ui\src\router
C:\no guardar\nucleo V3\planilla\ui\src\stores
C:\no guardar\nucleo V3\planilla\ui\src\tests
C:\no guardar\nucleo V3\planilla\ui\src\views
C:\no guardar\nucleo V3\planilla\ui\src\App.vue
C:\no guardar\nucleo V3\planilla\ui\src\main.js
C:\no guardar\nucleo V3\planilla\ui\src\style.css
C:\no guardar\nucleo V3\planilla\ui\src\assets\vue.svg
C:\no guardar\nucleo V3\planilla\ui\src\components\asistencias
C:\no guardar\nucleo V3\planilla\ui\src\components\chat
C:\no guardar\nucleo V3\planilla\ui\src\components\empleados
C:\no guardar\nucleo V3\planilla\ui\src\components\planillas
C:\no guardar\nucleo V3\planilla\ui\src\components\tareas
C:\no guardar\nucleo V3\planilla\ui\src\components\ui
C:\no guardar\nucleo V3\planilla\ui\src\components\HelloWorld.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\asistencias\cardAsistencia.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\asistencias\tablaAsistencias.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\chat\CanvasChat.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\empleados\cardEmpleado.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\empleados\tablaEmpleados.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\planillas\cardPlanilla.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\planillas\tablaPlanillas.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\tareas\cardTarea.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\tareas\tablaTareas.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\ui\navBar.vue
C:\no guardar\nucleo V3\planilla\ui\src\components\ui\sideDrawer.vue
C:\no guardar\nucleo V3\planilla\ui\src\router\index.js
C:\no guardar\nucleo V3\planilla\ui\src\stores\useAsistencias.js
C:\no guardar\nucleo V3\planilla\ui\src\stores\useChat.js
C:\no guardar\nucleo V3\planilla\ui\src\stores\useEmpleados.js
C:\no guardar\nucleo V3\planilla\ui\src\stores\usePlanillas.js
C:\no guardar\nucleo V3\planilla\ui\src\stores\useTareas.js
C:\no guardar\nucleo V3\planilla\ui\src\views\asistencias
C:\no guardar\nucleo V3\planilla\ui\src\views\empleados
C:\no guardar\nucleo V3\planilla\ui\src\views\planillas
C:\no guardar\nucleo V3\planilla\ui\src\views\tareas
C:\no guardar\nucleo V3\planilla\ui\src\views\ChatView.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\SettingsView.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\asistencias\AsistenciaForm.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\asistencias\AsistenciasIndex.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\empleados\EmpleadoForm.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\empleados\EmpleadosIndex.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\planillas\PlanillaForm.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\planillas\PlanillasIndex.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\tareas\TareaForm.vue
C:\no guardar\nucleo V3\planilla\ui\src\views\tareas\TareasIndex.vue
C:\no guardar\nucleo V3\planilla\worker\cron
C:\no guardar\nucleo V3\planilla\worker\prisma
C:\no guardar\nucleo V3\planilla\worker\.env

View File

@@ -1,13 +1,16 @@
<!doctype html>
<html lang="en">
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
<meta name="theme-color" content="#0d9488" /> <!-- color principal de la app -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Núcleo App</title>
<!-- Si luego querés PWA, activá el manifest -->
<!-- <link rel="manifest" href="/manifest.webmanifest" /> -->
</head>
<body>
<div id="app"></div>
<body class="min-h-screen flex flex-col bg-gray-100 text-gray-900 antialiased">
<div id="app" class="flex-1 flex flex-col"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

1516
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,10 +9,16 @@
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.13"
"@tailwindcss/vite": "^4.1.7",
"pinia": "^3.0.2",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.2",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.3",
"tailwindcss": "^4.1.7",
"vite": "^6.3.1"
}
}

View File

@@ -1,30 +1,24 @@
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TopBar from '@/components/ui/TopBar.vue'
import NavBar from '@/components/ui/NavBar.vue'
import { useUi } from '@/stores/useUi'
const ui = useUi()
</script>
<template>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
<!-- TopBar fija -->
<TopBar />
<!-- wrapper: deja espacio para TopBar (pt-14 = 56px) y, en desktop, para NavBar (pl-60) -->
<div :class="['pt-14 min-h-screen bg-gray-100 text-gray-900 transition-[padding-left] duration-200', ui.sidebarOpen ? 'md:pl-60' : '']">
<!-- NavBar fija -->
<NavBar />
<!-- contenido principal -->
<main class="min-h-[calc(100vh-56px)] flex flex-col overflow-hidden">
<RouterView class="flex-1 overflow-auto" />
</main>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
<style scoped></style>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,83 @@
<script setup>
import { ref, nextTick, onMounted, watch } from 'vue'
import { useChat } from '@/stores/useChat'
const chat = useChat()
const msg = ref('')
const list = ref(null)
function scrollBottom () {
nextTick(() => list.value?.scrollTo({ top: list.value.scrollHeight, behavior: 'smooth' }))
}
function send () {
const t = msg.value.trim()
if (!t) return
if (t.startsWith('/')) chat.run(t.slice(1))
else chat.add({ type: 'text', owner: 'yo', text: t })
msg.value = ''
scrollBottom()
}
function handleKey (e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
send()
}
}
onMounted(() => {
if (!chat.items.length) {
chat.add({ type: 'text', owner: 'bot', text: '¡Hola! Probá /empleados, /tareas, etc.' })
}
scrollBottom()
})
watch(() => chat.items.length, scrollBottom)
</script>
<template>
<!-- se adapta al contenedor flex, sin superponer la sidebar -->
<div class="flex flex-col flex-1 min-h-0 bg-gray-50">
<!-- historial -->
<div ref="list" class="flex-1 min-h-0 overflow-auto p-6 space-y-4 custom-scroll">
<template v-for="(m,i) in chat.items" :key="i">
<!-- mensaje de texto -->
<div :class="m.owner==='yo' ? 'flex justify-end' : 'flex justify-start'" v-if="m.type==='text'">
<div
class="max-w-lg rounded-lg px-4 py-2 shadow break-words"
:class="m.owner==='yo' ? 'bg-teal-600 text-white' : 'bg-white text-gray-900'">
{{ m.text }}
</div>
</div>
<!-- componente dinámico -->
<component v-else :is="m.is" v-bind="m.props" />
</template>
</div>
<!-- input -->
<form @submit.prevent="send" class="border-t bg-white p-4 flex gap-2">
<textarea
v-model="msg"
@keydown="handleKey"
rows="1"
placeholder="Escribí un mensaje… (Enter para enviar, Shift+Enter salto)"
class="flex-1 resize-none rounded-lg border p-3 focus:outline-none focus:ring-2 focus:ring-teal-500 custom-scroll"
/>
<button type="submit" class="px-4 py-2 rounded-lg bg-teal-600 text-white hover:bg-teal-700 transition">
</button>
</form>
</div>
</template>
<style scoped>
.custom-scroll::-webkit-scrollbar { width: 8px; }
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.35); border-radius: 4px; }
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.7); }
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(13,148,136,.6) transparent; }
</style>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,71 @@
<script setup>
import { ref, watch, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useUi } from '@/stores/useUi'
const ui = useUi()
// enlaces de la app
const links = [
{ to: '/', label: 'Chat', icon: '💬' },
{ to: '/empleados', label: 'Empleados', icon: '👥' },
{ to: '/tareas', label: 'Tareas', icon: '📋' },
{ to: '/planillas', label: 'Planillas', icon: '📂' },
{ to: '/asistencias', label: 'Asistencias', icon: '⏰' },
{ to: '/config', label: 'Config', icon: '⚙️' },
]
const route = useRoute()
const activePath = ref(route.path)
watch(route, v => (activePath.value = v.path))
// clases dinámicas p/ mostrar / ocultar barra
const sidebarClasses = computed(() => ui.sidebarOpen ? 'translate-x-0' : '-translate-x-full')
</script>
<template>
<!-- backdrop en mobile -->
<div v-if="ui.sidebarOpen" class="fixed inset-0 bg-black/40 md:hidden" @click="ui.closeSidebar" />
<!-- barra lateral -->
<aside
:class="['fixed left-0 top-0 md:top-14 h-screen w-60 bg-white dark:bg-zinc-900 border-r border-gray-200 dark:border-zinc-800 flex flex-col select-none z-50 transform transition-transform duration-200 ease-in-out', sidebarClasses]">
<!-- encabezado dentro de sidebar -->
<div class="flex items-center justify-between px-4 py-4 md:px-5 md:py-4 border-b border-gray-200 dark:border-zinc-800 md:border-none">
<span class="text-lg font-semibold text-teal-600 dark:text-teal-400 md:hidden">Núcleo</span>
<button class="h-8 w-8 inline-flex items-center justify-center text-gray-500 hover:text-teal-600" @click="ui.toggleSidebar">
</button>
</div>
<!-- navegación -->
<nav class="flex-1 overflow-y-auto custom-scroll pr-1 pt-4 md:pt-0">
<ul class="space-y-1 px-2">
<li v-for="l in links" :key="l.to">
<RouterLink
:to="l.to"
class="flex items-center gap-3 w-full px-3 py-2 rounded-md font-medium transition group"
:class="activePath.startsWith(l.to)
? 'bg-teal-600 text-white shadow'
: 'text-gray-700 dark:text-gray-100 hover:bg-teal-100 hover:text-teal-900 dark:hover:bg-zinc-800'"
@click="ui.closeSidebar()"
>
<span class="text-lg" aria-hidden="true">{{ l.icon }}</span>
<span class="truncate">{{ l.label }}</span>
</RouterLink>
</li>
</ul>
</nav>
</aside>
</template>
<style scoped>
ul { list-style: none; padding-left: 0; }
.custom-scroll::-webkit-scrollbar { width: 8px; }
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.4); border-radius: 4px; }
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.7); }
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(13,148,136,.6) transparent; }
</style>

View File

View File

@@ -0,0 +1,23 @@
<script setup>
import { useUi } from '@/stores/useUi'
const ui = useUi()
</script>
<template>
<!-- barra superior fija -->
<header class="fixed top-0 left-0 right-0 h-14 bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 flex items-center justify-between px-4 md:px-6 z-50 shadow-sm">
<!-- título -->
<h1 class="text-lg font-semibold tracking-wide text-teal-600 dark:text-teal-400 select-none">Núcleo</h1>
<!-- botón hamburguesa (visible solo en mobile) -->
<button
@click="ui.toggleSidebar"
class="inline-flex items-center justify-center h-9 w-9 rounded-md bg-teal-600 text-white hover:bg-teal-700 transition ">
&#9776;
</button>
</header>
</template>
<style scoped>
/* sin estilos extra */
</style>

View File

@@ -1,5 +1,14 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import router from './router'
createApp(App).mount('#app')
import App from './App.vue'
import './style.css' // Tailwind o tus estilos globales
const app =
createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

35
ui/src/router/index.js Normal file
View File

@@ -0,0 +1,35 @@
import { createRouter, createMemoryHistory } from 'vue-router'
const routes = [
// Chat principal y config
{ path: '/', name: 'chat', component: () => import('@/views/ChatView.vue') },
{ path: '/config', name: 'settings', component: () => import('@/views/SettingsView.vue') },
// ────── Empleados ──────
{ path: '/empleados', name: 'empleados-index', component: () => import('@/views/empleados/EmpleadosIndex.vue') },
{ path: '/empleados/nuevo', name: 'empleados-new', component: () => import('@/views/empleados/EmpleadoForm.vue') },
{ path: '/empleados/:id', name: 'empleados-edit', component: () => import('@/views/empleados/EmpleadoForm.vue'), props: true },
// ────── Tareas ──────
{ path: '/tareas', name: 'tareas-index', component: () => import('@/views/tareas/TareasIndex.vue') },
{ path: '/tareas/nuevo', name: 'tareas-new', component: () => import('@/views/tareas/TareaForm.vue') },
{ path: '/tareas/:id', name: 'tareas-edit', component: () => import('@/views/tareas/TareaForm.vue'), props: true },
// ────── Planillas ──────
{ path: '/planillas', name: 'planillas-index', component: () => import('@/views/planillas/PlanillasIndex.vue') },
{ path: '/planillas/nuevo', name: 'planillas-new', component: () => import('@/views/planillas/PlanillaForm.vue') },
{ path: '/planillas/:id', name: 'planillas-edit', component: () => import('@/views/planillas/PlanillaForm.vue'), props: true },
// ────── Asistencias ──────
{ path: '/asistencias', name: 'asistencias-index', component: () => import('@/views/asistencias/AsistenciasIndex.vue') },
{ path: '/asistencias/nuevo', name: 'asistencias-new', component: () => import('@/views/asistencias/AsistenciaForm.vue') },
{ path: '/asistencias/:id', name: 'asistencias-edit', component: () => import('@/views/asistencias/AsistenciaForm.vue'), props: true },
// 404
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFound.vue') }
]
export default createRouter({
history: createMemoryHistory(),
routes,
})

View File

@@ -0,0 +1,5 @@
import { defineStore } from 'pinia'
export const useAsistencias = defineStore('asistencias', {
state: () => ({ asistencias: [] }),
})

42
ui/src/stores/useChat.js Normal file
View File

@@ -0,0 +1,42 @@
import { defineStore } from 'pinia'
/**
* Chat store
* - items: hist de mensajes [{ type, owner, text, … }]
* - add(): agrega un mensaje
* - run(): ejecuta /comandos y mete componentes dinámicos
*/
export const useChat = defineStore('chat', {
state: () => ({
items: [],
}),
actions: {
add (item) {
this.items.push(item)
},
async run (cmd) {
switch (cmd) {
case 'empleados':
// para demo solo mostramos un texto, después inyectaremos un componente
this.add({ type: 'text', owner: 'bot', text: '🎉 Módulo Empleados aún en construcción.' })
break
case 'tareas':
this.add({ type: 'text', owner: 'bot', text: '🛠️ Módulo Tareas aún en construcción.' })
break
case 'planillas':
this.add({ type: 'text', owner: 'bot', text: '📂 Módulo Planillas en construcción.' })
break
case 'asistencias':
this.add({ type: 'text', owner: 'bot', text: '⏰ Módulo Asistencias en construcción.' })
break
default:
this.add({ type: 'text', owner: 'bot', text: `❓ No reconozco /${cmd}` })
}
},
},
})

View File

@@ -0,0 +1,12 @@
import { defineStore } from 'pinia'
export const useEmpleados = defineStore('empleados', {
state: () => ({ empleados: [] }),
actions: {
// placeholder para cargar/crear empleados
async fetchAll () {
// simulamos fetch
this.empleados = []
},
},
})

View File

@@ -0,0 +1,5 @@
import { defineStore } from 'pinia'
export const usePlanillas = defineStore('planillas', {
state: () => ({ planillas: [] }),
})

View File

@@ -0,0 +1,5 @@
import { defineStore } from 'pinia'
export const useTareas = defineStore('tareas', {
state: () => ({ tareas: [] }),
})

20
ui/src/stores/useUI.js Normal file
View File

@@ -0,0 +1,20 @@
// src/stores/useUi.js
import { defineStore } from 'pinia'
export const useUi = defineStore('ui', {
state: () => ({
sidebarOpen: true, // visible por defecto en desktop
}),
actions: {
toggleSidebar () {
this.sidebarOpen = !this.sidebarOpen
},
closeSidebar () {
this.sidebarOpen = false
},
openSidebar () {
this.sidebarOpen = true
},
},
})

View File

@@ -1,79 +1,4 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;

14
ui/src/views/ChatView.vue Normal file
View File

@@ -0,0 +1,14 @@
<script setup>
/* Vista raíz “/” → muestra el chat estilo ChatGPT */
import CanvasChat from '@/components/chat/CanvasChat.vue'
</script>
<template>
<div class="h-full flex flex-col">
<CanvasChat class="flex-1" />
</div>
</template>
<style scoped>
/* nada por ahora */
</style>

21
ui/src/views/NotFound.vue Normal file
View File

@@ -0,0 +1,21 @@
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
</script>
<template>
<div class="h-full flex flex-col items-center justify-center gap-6 p-8 text-center">
<h1 class="text-5xl font-bold text-teal-600">404</h1>
<p class="text-lg text-gray-700">Uy, no encontramos esa página.</p>
<button
@click="router.push('/')"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-teal-600 text-white hover:bg-teal-700 transition">
Volver al inicio
</button>
</div>
</template>
<style scoped>
/* Estilos extra opcionales */
</style>

View File

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -1,7 +1,17 @@
// vite.config.js
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import path from 'node:path'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [
vue(),
tailwindcss(),
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'), // ← apunta a /ui/src
},
},
})