Compare commits

...

2 commits

15 changed files with 372 additions and 668 deletions

10
.gram/debug.jsonc Normal file
View file

@ -0,0 +1,10 @@
[
{
"adapter": "JavaScript",
"type": "chrome",
"request": "attach",
"port": 9222,
"label": "Conectar a navegador",
"webRoot": "${GRAM_WORKTREE_ROOT}/src",
},
]

3
.gram/settings.jsonc Normal file
View file

@ -0,0 +1,3 @@
{
"language_servers": ["...", "!biome"],
}

4
.rumdl.toml Normal file
View file

@ -0,0 +1,4 @@
[global]
flavor = "gfm"
[MD013]
reflow = true

12
api-tests/profile.hurl Normal file
View file

@ -0,0 +1,12 @@
POST http://localhost:5000/api/auth/login
{
"email": "test@test.com",
"password": "123123"
}
HTTP 200
[Captures]
token: jsonpath "$.token"
GET http://localhost:5000/api/auth/me
Authorization: Bearer {{token}}
HTTP 200

View file

@ -46,9 +46,11 @@ body:
### Checkout & Profile
Esta ruta requiere un token JWT en el header, el token se obtiene en el endpoint de Auth explicado en el item siguiente (JWT).
Esta ruta requiere un token JWT en el header, el token se obtiene en el endpoint
de Auth explicado en el item siguiente (JWT).
Además puedes enviar un carrito con los productos a comprar, esto es solo una simulación, no se guarda en la base de datos.
Además puedes enviar un carrito con los productos a comprar, esto es solo una
simulación, no se guarda en la base de datos.
```sh
POST /api/checkouts
@ -62,7 +64,8 @@ body:
}
```
Endpoint para obtener el perfil del usuario autenticado. Necesitas enviar el token JWT en el header.
Endpoint para obtener el perfil del usuario autenticado. Necesitas enviar el
token JWT en el header.
```sh
GET /api/auth/me
@ -70,7 +73,8 @@ GET /api/auth/me
## JWT
Para obtener el token JWT, se debe hacer una petición a `/api/auth/login` o a `/api/auth/register` con el body correspondiente.
Para obtener el token JWT, se debe hacer una petición a `/api/auth/login` o a
`/api/auth/register` con el body correspondiente.
El token JWT se debe enviar en el header `Authorization` de la siguiente manera:

View file

@ -1,231 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"files": { "ignoreUnknown": false },
"formatter": { "enabled": true, "indentStyle": "tab" },
"linter": {
"enabled": true,
"rules": { "recommended": false },
"includes": ["**", "!dist"]
},
"javascript": { "formatter": { "quoteStyle": "double" } },
"overrides": [
{
"includes": ["**/*.{js,jsx}"],
"linter": {
"rules": {
"complexity": {
"noAdjacentSpacesInRegex": "error",
"noExtraBooleanCast": "error",
"noUselessCatch": "error",
"noUselessEscapeInRegex": "error"
},
"correctness": {
"noConstAssign": "error",
"noConstantCondition": "error",
"noEmptyCharacterClassInRegex": "error",
"noEmptyPattern": "error",
"noGlobalObjectCalls": "error",
"noInvalidBuiltinInstantiation": "error",
"noInvalidConstructorSuper": "error",
"noNonoctalDecimalEscape": "error",
"noPrecisionLoss": "error",
"noSelfAssign": "error",
"noSetterReturn": "error",
"noSwitchDeclarations": "error",
"noUndeclaredVariables": "error",
"noUnreachable": "error",
"noUnreachableSuper": "error",
"noUnsafeFinally": "error",
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedPrivateClassMembers": "error",
"noUnusedVariables": "error",
"useIsNan": "error",
"useValidForDirection": "error",
"useValidTypeof": "error",
"useYield": "error"
},
"suspicious": {
"noAsyncPromiseExecutor": "error",
"noCatchAssign": "error",
"noClassAssign": "error",
"noCompareNegZero": "error",
"noConstantBinaryExpressions": "error",
"noControlCharactersInRegex": "error",
"noDebugger": "error",
"noDuplicateCase": "error",
"noDuplicateClassMembers": "error",
"noDuplicateElseIf": "error",
"noDuplicateObjectKeys": "error",
"noDuplicateParameters": "error",
"noEmptyBlockStatements": "error",
"noFallthroughSwitchClause": "error",
"noFunctionAssign": "error",
"noGlobalAssign": "error",
"noImportAssign": "error",
"noIrregularWhitespace": "error",
"noMisleadingCharacterClass": "error",
"noPrototypeBuiltins": "error",
"noRedeclare": "error",
"noShadowRestrictedNames": "error",
"noSparseArray": "error",
"noUnsafeNegation": "error",
"noUselessRegexBackrefs": "error",
"noWith": "error",
"useGetterReturn": "error"
}
}
}
},
{
"includes": ["**/*.{js,jsx}"],
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": "warn",
"useHookAtTopLevel": "error"
}
}
}
},
{ "includes": ["**/*.{js,jsx}"], "linter": { "rules": {} } },
{
"includes": ["**/*.{js,jsx}"],
"javascript": {
"globals": [
"onanimationend",
"ongamepadconnected",
"onlostpointercapture",
"onanimationiteration",
"onkeyup",
"onmousedown",
"onanimationstart",
"onslotchange",
"onprogress",
"ontransitionstart",
"onpause",
"onended",
"onpointerover",
"onscrollend",
"onformdata",
"ontransitionrun",
"onanimationcancel",
"ondrag",
"onchange",
"onbeforeinstallprompt",
"onbeforexrselect",
"onmessage",
"ontransitioncancel",
"onpointerdown",
"onabort",
"onpointerout",
"oncuechange",
"ongotpointercapture",
"onscrollsnapchanging",
"onsearch",
"onsubmit",
"onstalled",
"onsuspend",
"onreset",
"onerror",
"onmouseenter",
"ongamepaddisconnected",
"onresize",
"ondragover",
"onbeforetoggle",
"onmouseover",
"onpagehide",
"onmousemove",
"onratechange",
"oncommand",
"onmessageerror",
"onwheel",
"ondevicemotion",
"onauxclick",
"ontransitionend",
"onpaste",
"onpageswap",
"ononline",
"ondeviceorientationabsolute",
"onkeydown",
"onclose",
"onselect",
"onpageshow",
"onpointercancel",
"onbeforematch",
"onpointerrawupdate",
"ondragleave",
"onscrollsnapchange",
"onseeked",
"onwaiting",
"onbeforeunload",
"onplaying",
"onvolumechange",
"ondragend",
"onstorage",
"onloadeddata",
"onfocus",
"onoffline",
"onplay",
"onafterprint",
"onclick",
"oncut",
"onmouseout",
"ondblclick",
"oncanplay",
"onloadstart",
"onappinstalled",
"onpointermove",
"ontoggle",
"oncontextmenu",
"onblur",
"oncancel",
"onbeforeprint",
"oncontextrestored",
"onloadedmetadata",
"onpointerup",
"onlanguagechange",
"oncopy",
"onselectstart",
"onscroll",
"onload",
"ondragstart",
"onbeforeinput",
"oncanplaythrough",
"oninput",
"oninvalid",
"ontimeupdate",
"ondurationchange",
"onselectionchange",
"onmouseup",
"location",
"onkeypress",
"onpointerleave",
"oncontextlost",
"ondrop",
"onsecuritypolicyviolation",
"oncontentvisibilityautostatechange",
"ondeviceorientation",
"onseeking",
"onrejectionhandled",
"onunload",
"onmouseleave",
"onhashchange",
"onpointerenter",
"onmousewheel",
"onunhandledrejection",
"ondragenter",
"onpopstate",
"onpagereveal",
"onemptied"
]
},
"linter": { "rules": { "correctness": { "noUnusedVariables": "error" } } }
}
],
"assist": {
"enabled": true,
"actions": { "source": { "organizeImports": "on" } }
}
}

View file

@ -1,12 +1,15 @@
mise-task task:
mise r {{task}}
mise r {{ task }}
setup:
mise x -- aube i
cd backend && npm i
mise x -- aube i
cd backend && npm i
start-backend:
cd backend && npm start
cd backend && npm start
start-frontend:
mise r dev
mise r dev
start:
@mprocs

View file

@ -2,10 +2,10 @@
aube = "latest"
mprocs = "latest"
oxlint = "latest"
pitchfork = "latest"
pnpm = "latest"
prek = "latest"
oxfmt = "latest"
node = "24"
[tasks.dev]
description = "Arranca el servidor dev"

View file

@ -1,33 +1,35 @@
{
"name": "desafio",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "biome check",
"preview": "vite preview"
},
"dependencies": {
"radashi": "^12.7.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.14.2"
},
"devDependencies": {
"@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0",
"@eslint/js": "^9.39.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@unocss/reset": "^66.6.7",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"unocss": "^66.6.7",
"vite": "^8.0.1"
}
"name": "desafio",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "biome check",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-form": "^1.32.0",
"radashi": "^12.7.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.14.2",
"zod": "^4.4.3"
},
"devDependencies": {
"@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0",
"@eslint/js": "^9.39.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@unocss/reset": "^66.6.7",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"unocss": "^66.6.7",
"vite": "^8.0.1"
}
}

View file

@ -5,12 +5,23 @@ settings:
excludeLinksFromLockfile: false
time:
'@tanstack/devtools-event-client@0.4.3': 2026-03-11T13:42:37.747Z
'@tanstack/form-core@1.32.0': 2026-05-10T22:35:01.878Z
'@tanstack/pacer-lite@0.1.1': 2025-12-06T05:29:43.362Z
'@tanstack/react-form@1.32.0': 2026-05-10T22:35:03.027Z
'@tanstack/react-store@0.9.3': 2026-03-25T17:43:08.336Z
'@tanstack/store@0.9.3': 2026-03-25T17:43:08.346Z
radashi@12.7.2: 2026-02-24T20:32:30.146Z
use-sync-external-store@1.6.0: 2025-10-01T21:39:12.499Z
zod@4.4.3: 2026-05-04T07:06:40.819Z
importers:
.:
dependencies:
'@tanstack/react-form':
specifier: ^1.32.0
version: 1.32.0(react@19.2.4)
'@types/node':
specifier: ^20.19.0 || >=22.12.0
version: 25.6.0
@ -29,6 +40,9 @@ importers:
react-router-dom:
specifier: ^7.14.2
version: 7.14.2(react@19.2.4)(react-dom@19.2.4(react@19.2.4))
zod:
specifier: ^4.4.3
version: 4.4.3
devDependencies:
'@commitlint/cli':
specifier: ^20.5.0
@ -299,34 +313,6 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@oxc-parser/binding-darwin-arm64@0.115.0':
resolution: {integrity: sha512-ii/oOZjfGY1aszXTy29Z5DRyCEnBOrAXDVCvfdfXFQsOZlbbOa7NMHD7D+06YFe5qdxfmbWAYv4yn6QJi/0d2g==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- darwin
cpu:
- arm64
'@oxc-parser/binding-linux-arm64-gnu@0.115.0':
resolution: {integrity: sha512-1ej/MjuTY9tJEunU/hUPIFmgH5PqgMQoRjNOvOkibtJ3Zqlw/+Lc+HGHDNET8sjbgIkWzdhX+p4J96A5CPdbag==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- linux
cpu:
- arm64
libc:
- glibc
'@oxc-parser/binding-linux-arm64-musl@0.115.0':
resolution: {integrity: sha512-HjsZbJPH9mMd4swJRywVMsDZsJX0hyKb1iNHo5ijRl5yhtbO3lj7ImSrrL1oZ1VEg0te4iKmDGGz/6YPLd1G8w==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- linux
cpu:
- arm64
libc:
- musl
'@oxc-parser/binding-linux-x64-gnu@0.115.0':
resolution: {integrity: sha512-9iVX789DoC3SaOOG+X6NcF/tVChgLp2vcHffzOC2/Z1JTPlz6bMG2ogvcW6/9s0BG2qvhNQImd+gbWYeQbOwVw==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -337,32 +323,6 @@ packages:
libc:
- glibc
'@oxc-parser/binding-linux-x64-musl@0.115.0':
resolution: {integrity: sha512-RmQmk+mjCB0nMNfEYhaCxwofLo1Z95ebHw1AGvRiWGCd4zhCNOyskgCbMogIcQzSB3SuEKWgkssyaiQYVAA4hQ==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- linux
cpu:
- x64
libc:
- musl
'@oxc-parser/binding-win32-arm64-msvc@0.115.0':
resolution: {integrity: sha512-/ym+Absk/TLFvbhh3se9XYuI1D7BrUVHw4RaG/2dmWKgBenrZHaJsgnRb7NJtaOyjEOLIPtULx1wDdVL0SX2eg==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- win32
cpu:
- arm64
'@oxc-parser/binding-win32-x64-msvc@0.115.0':
resolution: {integrity: sha512-oxUl82N+fIO9jIaXPph8SPPHQXrA08BHokBBJW8ct9F/x6o6bZE6eUAhUtWajbtvFhL8UYcCWRMba+kww6MBlA==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- win32
cpu:
- x64
'@oxc-project/types@0.115.0':
resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==}
@ -375,34 +335,6 @@ packages:
'@quansync/fs@1.0.0':
resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
'@rolldown/binding-darwin-arm64@1.0.0-rc.11':
resolution: {integrity: sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- darwin
cpu:
- arm64
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11':
resolution: {integrity: sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- linux
cpu:
- arm64
libc:
- glibc
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.11':
resolution: {integrity: sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- linux
cpu:
- arm64
libc:
- musl
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.11':
resolution: {integrity: sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -413,32 +345,6 @@ packages:
libc:
- glibc
'@rolldown/binding-linux-x64-musl@1.0.0-rc.11':
resolution: {integrity: sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- linux
cpu:
- x64
libc:
- musl
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11':
resolution: {integrity: sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- win32
cpu:
- arm64
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.11':
resolution: {integrity: sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==}
engines: {node: ^20.19.0 || >=22.12.0}
os:
- win32
cpu:
- x64
'@rolldown/pluginutils@1.0.0-rc.11':
resolution: {integrity: sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==}
@ -453,6 +359,35 @@ packages:
resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==}
engines: {node: '>=18'}
'@tanstack/devtools-event-client@0.4.3':
resolution: {integrity: sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==}
engines: {node: '>=18'}
hasBin: true
'@tanstack/form-core@1.32.0':
resolution: {integrity: sha512-Tn5VRDSjyqjmaet2tJMuEWDRFyrCaon03vxXPlSSaiSs6C/N7lCIwGCXJbZXEUq1kTj8jYN9qyXHbsz4LQHcow==}
'@tanstack/pacer-lite@0.1.1':
resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==}
engines: {node: '>=18'}
'@tanstack/react-form@1.32.0':
resolution: {integrity: sha512-6WP5SQTA6/H9crCpvpq3ZppYWqtrdE5NjOy6ebABi6uAQPqhfTzrdjS9t40mCZCFtGI5585OhJV6zBP/KN2zcw==}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@tanstack/react-start':
optional: true
'@tanstack/react-store@0.9.3':
resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/store@0.9.3':
resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@ -837,12 +772,6 @@ packages:
flatted@3.4.2:
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os:
- darwin
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@ -974,34 +903,6 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
os:
- darwin
cpu:
- arm64
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
os:
- linux
cpu:
- arm64
libc:
- glibc
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
os:
- linux
cpu:
- arm64
libc:
- musl
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
@ -1012,32 +913,6 @@ packages:
libc:
- glibc
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
os:
- linux
cpu:
- x64
libc:
- musl
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
os:
- win32
cpu:
- arm64
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
os:
- win32
cpu:
- x64
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
@ -1364,6 +1239,11 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
use-sync-external-store@1.6.0:
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
vite@8.0.2:
resolution: {integrity: sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -1451,6 +1331,9 @@ packages:
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
snapshots:
'@antfu/install-pkg@1.1.0':
@ -1764,27 +1647,9 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@oxc-parser/binding-darwin-arm64@0.115.0':
optional: true
'@oxc-parser/binding-linux-arm64-gnu@0.115.0':
optional: true
'@oxc-parser/binding-linux-arm64-musl@0.115.0':
optional: true
'@oxc-parser/binding-linux-x64-gnu@0.115.0':
optional: true
'@oxc-parser/binding-linux-x64-musl@0.115.0':
optional: true
'@oxc-parser/binding-win32-arm64-msvc@0.115.0':
optional: true
'@oxc-parser/binding-win32-x64-msvc@0.115.0':
optional: true
'@oxc-project/types@0.115.0': {}
'@oxc-project/types@0.122.0': {}
@ -1795,27 +1660,9 @@ snapshots:
dependencies:
quansync: 1.0.0
'@rolldown/binding-darwin-arm64@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.11':
optional: true
'@rolldown/binding-linux-x64-musl@1.0.0-rc.11':
optional: true
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11':
optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.11':
optional: true
'@rolldown/pluginutils@1.0.0-rc.11': {}
'@rolldown/pluginutils@1.0.0-rc.7': {}
@ -1826,6 +1673,31 @@ snapshots:
'@simple-libs/stream-utils@1.2.0': {}
'@tanstack/devtools-event-client@0.4.3': {}
'@tanstack/form-core@1.32.0':
dependencies:
'@tanstack/devtools-event-client': 0.4.3
'@tanstack/pacer-lite': 0.1.1
'@tanstack/store': 0.9.3
'@tanstack/pacer-lite@0.1.1': {}
'@tanstack/react-form@1.32.0(react@19.2.4)':
dependencies:
'@tanstack/form-core': 1.32.0
'@tanstack/react-store': 0.9.3(react@19.2.4)(react-dom@19.2.4(react@19.2.4))
react: 19.2.4
'@tanstack/react-store@0.9.3(react@19.2.4)(react-dom@19.2.4(react@19.2.4))':
dependencies:
'@tanstack/store': 0.9.3
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
use-sync-external-store: 1.6.0(react@19.2.4)
'@tanstack/store@0.9.3': {}
'@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {}
@ -2259,9 +2131,6 @@ snapshots:
flatted@3.4.2: {}
fsevents@2.3.3:
optional: true
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
@ -2358,38 +2227,14 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lightningcss-darwin-arm64@1.32.0:
optional: true
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
lightningcss-linux-arm64-musl@1.32.0:
optional: true
lightningcss-linux-x64-gnu@1.32.0:
optional: true
lightningcss-linux-x64-musl@1.32.0:
optional: true
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
lightningcss-win32-x64-msvc@1.32.0:
optional: true
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-darwin-arm64: 1.32.0
lightningcss-linux-arm64-gnu: 1.32.0
lightningcss-linux-arm64-musl: 1.32.0
lightningcss-linux-x64-gnu: 1.32.0
lightningcss-linux-x64-musl: 1.32.0
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
lines-and-columns@1.2.4: {}
@ -2477,13 +2322,7 @@ snapshots:
dependencies:
'@oxc-project/types': 0.115.0
optionalDependencies:
'@oxc-parser/binding-darwin-arm64': 0.115.0
'@oxc-parser/binding-linux-arm64-gnu': 0.115.0
'@oxc-parser/binding-linux-arm64-musl': 0.115.0
'@oxc-parser/binding-linux-x64-gnu': 0.115.0
'@oxc-parser/binding-linux-x64-musl': 0.115.0
'@oxc-parser/binding-win32-arm64-msvc': 0.115.0
'@oxc-parser/binding-win32-x64-msvc': 0.115.0
oxc-walker@0.7.0(oxc-parser@0.115.0):
dependencies:
@ -2581,13 +2420,7 @@ snapshots:
'@oxc-project/types': 0.122.0
'@rolldown/pluginutils': 1.0.0-rc.11
optionalDependencies:
'@rolldown/binding-darwin-arm64': 1.0.0-rc.11
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.11
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.11
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.11
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.11
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.11
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.11
scheduler@0.27.0: {}
@ -2705,6 +2538,10 @@ snapshots:
dependencies:
punycode: 2.3.1
use-sync-external-store@1.6.0(react@19.2.4):
dependencies:
react: 19.2.4
vite@8.0.2(@types/node@25.6.0)(jiti@2.6.1):
dependencies:
lightningcss: 1.32.0
@ -2714,7 +2551,6 @@ snapshots:
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.6.0
fsevents: 2.3.3
jiti: 2.6.1
webpack-virtual-modules@0.6.2: {}
@ -2754,3 +2590,5 @@ snapshots:
zod: 4.3.6
zod@4.3.6: {}
zod@4.4.3: {}

View file

@ -1,7 +1,7 @@
[[repos]]
hooks = [
{ id = "commitlint", name = "commitlint", language = "system", entry = "mise x -- aubx commitlint -e", stages = [
"commit-msg",
] },
{ id = "commitlint", name = "commitlint", language = "system", entry = "mise x -- aubx commitlint -e", stages = [
"commit-msg",
] },
]
repo = "local"

View file

@ -9,7 +9,7 @@ const UserProvider = ({ children }) => {
const storeData = (email, token) => {
setEmail(email);
setToken(token);
localStorage.setItem("loginToken", data.token);
localStorage.setItem("loginToken", token);
};
const login = async (user, password) => {
const res = await fetch("http://localhost:5000/api/auth/login", {
@ -40,8 +40,29 @@ const UserProvider = ({ children }) => {
setToken(null);
setEmail(null);
};
const getProfile = async () => {
const res = await fetch("http://localhost:5000/api/auth/me", {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
throw new Error("Error al obtener perfil", res.status);
}
const data = await res.json();
return data;
};
return (
<UserContext.Provider value={{ token, setToken, email, setEmail, login, register, logout }}>
<UserContext.Provider
value={{
token,
setToken,
email,
setEmail,
login,
register,
getProfile,
logout,
}}
>
{children}
</UserContext.Provider>
);

View file

@ -1,65 +1,89 @@
import { useContext, useState } from "react";
import { CartContext } from "../context/CartContext";
import { pizzaCart } from "../pizzas";
import { UserContext } from "../context/UserContext";
const Cart = () => {
const { token } = useContext(UserContext);
const { cart, setCart } = useContext(CartContext);
const { cart, setCart, getTotal } = useContext(CartContext);
const [success, setSuccess] = useState(false);
return (
<>
<h1 className="text-4xl font-bold">Carrito</h1>
<div id="cartContainer" className="flex gap-4 flex-col">
{cart.map((pizza) => (
<div
className="border-3 border-lime-300 rounded-md flex gap-4 overflow-hidden"
key={pizza.id}
>
<img src={pizza.img} className="w-32 rounded-r-sm" />
<div>
<h2>Pizza {pizza.name}</h2>
<div className="flex gap-4">
<button
onClick={() => {
setCart((cart) =>
cart.map((p) => (p.id === pizza.id ? { ...p, count: p.count + 1 } : p)),
);
}}
className="bg-blue-500 px-4 text-white rounded-md"
>
+
</button>
<p className="text-grey-500">{pizza.count}</p>
<button
onClick={() => {
setCart((cart) =>
cart
.map((p) => (p.id === pizza.id ? { ...p, count: p.count - 1 } : p))
.filter((p) => p.count > 0),
);
}}
className="bg-red-500 px-4 text-white rounded-md"
>
-
</button>
{!success ? (
<>
<div id="cartContainer" className="flex gap-4 flex-col">
{cart.map((pizza) => (
<div
className="border-3 border-lime-300 rounded-md flex gap-4 overflow-hidden"
key={pizza.id}
>
<img src={pizza.img} className="w-32 rounded-r-sm" />
<div>
<h2>Pizza {pizza.name}</h2>
<div className="flex gap-4">
<button
onClick={() => {
setCart((cart) =>
cart.map((p) =>
p.id === pizza.id
? { ...p, count: p.count + 1 }
: p,
),
);
}}
className="bg-blue-500 px-4 text-white rounded-md"
>
+
</button>
<p className="text-grey-500">{pizza.count}</p>
<button
onClick={() => {
setCart((cart) =>
cart
.map((p) =>
p.id === pizza.id
? { ...p, count: p.count - 1 }
: p,
)
.filter((p) => p.count > 0),
);
}}
className="bg-red-500 px-4 text-white rounded-md"
>
-
</button>
</div>
</div>
</div>
</div>
))}
<p className="text-3xl">
Total:
<strong>{` $${getTotal().toLocaleString("es-CL")}`}</strong>
</p>
</div>
))}
<p className="text-3xl">
Total:
<strong>
{` $${cart.reduce((acc, it) => acc + it.price * it.count, 0).toLocaleString("es-CL")}`}
</strong>
</p>
</div>
<button
type="submit"
disabled={!token}
className="bg-black text-white rounded-md p-2 text-lg hover:bg-gray-500 disabled:bg-gray-200"
>
Pagar
</button>
<button
type="button"
disabled={!token}
className="bg-black text-white rounded-md p-2 text-lg hover:bg-gray-500 disabled:bg-gray-200"
onClick={async () => {
const res = await fetch("http://localhost:5000/api/checkouts", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ cart }),
});
if (!res.ok) {
console.error(res);
return;
}
setSuccess(true);
}}
>
Pagar
</button>
</>
) : (
<p class="text-4xl text-teal-500">¡Compra hecha con éxito!</p>
)}
</>
);
};

View file

@ -4,12 +4,15 @@ import { useContext } from "react";
// Librería recomendada en tutoría
import { z } from "zod";
import { UserContext } from "../context/UserContext";
import { useNavigate } from "react-router-dom";
// Esquema de validación de login
const loginSchema = z.object({
user: z.email({ error: "Email inválido" }).nonempty({ error: "Se requiere un email" }),
password: z.string().min(6, { error: "La contraseña debe tener al menos 6 caracteres" }),
user: z
.email({ error: "Email inválido" })
.nonempty({ error: "Se requiere un email" }),
password: z
.string()
.min(6, { error: "La contraseña debe tener al menos 6 caracteres" }),
});
const Login = () => {
@ -21,6 +24,7 @@ const Login = () => {
try {
await login(user, password);
} catch (error) {
console.error(error);
alert("Error de login");
}
},

View file

@ -1,9 +1,19 @@
import { useContext } from "react";
import { useContext, useState } from "react";
import { UserContext } from "../context/UserContext";
import { useEffect } from "react";
const Profile = () => {
const email = "test@example.com";
const { logout } = useContext(UserContext);
const [email, setEmail] = useState("");
const { getProfile, logout } = useContext(UserContext);
useEffect(() => {
getProfile()
.then((data) => {
setEmail(data.email);
})
.catch((error) => {
console.error(error);
});
});
return (
<div>
<p>