diff --git a/apps/web/src/lib/styles/base.css b/apps/web/src/lib/styles/base.css
new file mode 100644
index 0000000..5481929
--- /dev/null
+++ b/apps/web/src/lib/styles/base.css
@@ -0,0 +1,63 @@
+/* Reset/normalización ligera */
+*,
+*::before,
+*::after { box-sizing: border-box; }
+html, body { height: 100%; }
+body {
+ margin: 0;
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji";
+ font-size: 16px;
+ line-height: 1.5;
+ background: var(--color-bg);
+ color: var(--color-text);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+img, svg, video { display: block; max-width: 100%; }
+a { color: inherit; text-decoration: none; }
+button { font: inherit; }
+
+/* Accesibilidad: foco visible */
+:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+}
+
+/* Utilidades */
+.container {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 0 var(--space-4);
+}
+
+.sr-only {
+ position: absolute !important;
+ width: 1px; height: 1px;
+ padding: 0; margin: -1px;
+ overflow: hidden; clip: rect(0,0,0,0);
+ white-space: nowrap; border: 0;
+}
+
+/* Controles base */
+button,
+input[type="submit"] {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 44px;
+ padding: 0 12px;
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-sm);
+ background: var(--color-surface);
+ color: var(--color-text);
+ cursor: pointer;
+}
+button.primary {
+ background: var(--color-primary);
+ border-color: var(--color-primary);
+ color: white;
+}
+button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
diff --git a/apps/web/src/lib/styles/tokens.css b/apps/web/src/lib/styles/tokens.css
new file mode 100644
index 0000000..07209a8
--- /dev/null
+++ b/apps/web/src/lib/styles/tokens.css
@@ -0,0 +1,39 @@
+:root {
+ --color-bg: #ffffff;
+ --color-surface: #f7f7f8;
+ --color-text: #111111;
+ --color-text-muted: #555555;
+ --color-border: #e5e7eb;
+
+ --color-primary: #2563eb; /* azul */
+ --color-danger: #dc2626; /* rojo */
+ --color-warning: #d97706; /* ámbar */
+ --color-success: #16a34a; /* verde */
+
+ --radius-sm: 6px;
+ --radius-md: 8px;
+
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.06);
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.08);
+
+ --space-1: 4px;
+ --space-2: 8px;
+ --space-3: 12px;
+ --space-4: 16px;
+ --space-5: 24px;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-bg: #0b0c0f;
+ --color-surface: #14161a;
+ --color-text: #e6e7eb;
+ --color-text-muted: #a1a1aa;
+ --color-border: #26272b;
+
+ --color-primary: #60a5fa;
+ --color-danger: #f87171;
+ --color-warning: #fbbf24;
+ --color-success: #34d399;
+ }
+}
diff --git a/apps/web/src/lib/ui/layout/AppShell.svelte b/apps/web/src/lib/ui/layout/AppShell.svelte
new file mode 100644
index 0000000..d1e38ae
--- /dev/null
+++ b/apps/web/src/lib/ui/layout/AppShell.svelte
@@ -0,0 +1,70 @@
+
+
+
+
+
+ {@render children?.()}
+
+
+
diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte
index 20f8d04..f4b1d6d 100644
--- a/apps/web/src/routes/+layout.svelte
+++ b/apps/web/src/routes/+layout.svelte
@@ -1,11 +1,14 @@
+
{@render children?.()}
diff --git a/apps/web/src/routes/app/+layout.server.ts b/apps/web/src/routes/app/+layout.server.ts
index 7164642..79628c5 100644
--- a/apps/web/src/routes/app/+layout.server.ts
+++ b/apps/web/src/routes/app/+layout.server.ts
@@ -4,7 +4,7 @@ import { redirect } from '@sveltejs/kit';
export const load: LayoutServerLoad = async (event) => {
const userId = event.locals.userId ?? null;
if (!userId) {
- throw redirect(303, '/');
+ throw redirect(303, '/login');
}
return { userId };
};
diff --git a/apps/web/src/routes/app/+layout.svelte b/apps/web/src/routes/app/+layout.svelte
new file mode 100644
index 0000000..87f6323
--- /dev/null
+++ b/apps/web/src/routes/app/+layout.svelte
@@ -0,0 +1,8 @@
+
+
+
+ {@render children?.()}
+