Imported from kurtneumannle/web1-examen (
AGENTS.md). Install upstream withnpx skills add kurtneumannle/web1-examen. Copyright stays with the author.
This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory; in monorepos the next package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by next dev — verify at node_modules/next/dist/server/lib/generate-agent-files.js. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
VentasFix — Guía para agentes
Monolito Next.js 16 (App Router): un proceso sirve el backoffice y la API REST. Contexto del examen en docs/brief.md, requerimiento en docs/requerimiento.md, stack en docs/stack.md, plan ejecutado en docs/plan.md.
Versiones fijadas (no actualizar sin motivo)
| Paquete | Versión | Por qué está fijada |
|---|---|---|
next |
16.3.5 | La convención es proxy.ts, no middleware.ts |
prisma / @prisma/client |
6.19.3 exacta | El dist-tag latest de npm apunta a 8.0.0-rc.x (release candidate). Prisma 7+ elimina url del datasource y exige driver adapters |
next-auth |
5.0.0-beta.32 | Beta; ver "Trampas conocidas" |
@node-rs/argon2 |
2.x | Binarios precompilados; no usar el paquete argon2 (requiere node-gyp) |
Arquitectura: regla no negociable
Toda escritura pasa por src/lib/models/*. Ni las server actions ni los route handlers llaman a Prisma directamente para crear, actualizar o eliminar.
Backoffice (src/actions/*) ─┐
├─→ src/lib/models/* ─→ src/lib/prisma.ts ─→ SQLite
API REST (src/app/api/**) ─┘ ↑
IVA, hash, validación, limpieza de imágenes
Motivo: hay dos puertas de entrada al mismo dominio. Si una regla (IVA, hash Argon2, borrado de archivo de imagen) vive en una sola puerta, la otra la incumple. Ya ocurrió una vez: el DELETE de la API dejaba imágenes huérfanas porque la limpieza estaba solo en la server action.
Convenciones de código
Idioma
- Dominio en español:
crearProducto,precioNeto,rutEmpresa,razonSocial. Coincide con el enunciado y con las columnas de la base de datos. - Infraestructura en inglés:
requireApiAuth,hashPassword,FieldErrors,DataTable. - Comentarios y mensajes de usuario en español. Referencias a reglas del enunciado como
(R5),(A3).
Imports: dos regímenes, a propósito
| Capa | Estilo | Razón |
|---|---|---|
src/lib/** (modelos, prisma, password, upload, validate, api-token) |
Relativo con extensión: import { prisma } from "../prisma.ts" |
Estos archivos los ejecuta también Node directo (prisma/seed.ts, scripts). El resolver ESM nativo de Node no entiende el alias @/ ni omitir extensiones |
src/app/**, src/actions/**, src/components/** |
Alias: import { Button } from "@/components/ui/button" |
Solo corren dentro del bundler de Next |
auth.ts, auth.config.ts, src/proxy.ts |
Relativo sin extensión | Viven en la raíz, fuera del alcance de @/* (que mapea a ./src/*) |
allowImportingTsExtensions: true en tsconfig.json habilita el primer régimen.
Restricción: Node ejecuta TypeScript en modo strip-only
src/lib/** y prisma/seed.ts corren bajo el type-stripping nativo de Node. Está prohibido ahí:
- Propiedades de parámetro en constructores (
constructor(public readonly x: T)) — usar campo declarado + asignación explícita (verValidationErrorensrc/lib/validate.ts). enum,namespace, decoradores.
Validación y errores
requireFields(data, campos)devuelveFieldErrors(Record<campo, mensaje>);throwIfInvalid(errors)lanzaValidationError.- Ambas puertas atrapan
ValidationErrory devuelven el mismo mapa de errores: la server action lo retorna parauseActionState, el route handler lo emite como400 { errors }. - Un error que no sea
ValidationErrorse re-lanza. Nunca se traga.
Funciones de escritura en modelos: siempre async
// Correcto: el throw síncrono de la validación se convierte en rechazo de promesa.
export async function crearCliente(data: ClienteInput) { … }
Una función no-async que valida y lanza de forma síncrona rompe a cualquier llamador que espere una promesa rechazada.
Tipos
- Prohibido publicar contratos con
ReturnType<typeof fn>oAwaited<ReturnType<…>>. El módulo dueño exporta un tipo nombrado; el consumidor lo importa.- Ejemplo:
UsuarioPublicoensrc/lib/models/usuarios.ts(derivado conPrisma.UsuarioGetPayload). - Entidades sin
selectusan directamenteProducto/Clientede@prisma/client.
- Ejemplo:
- Sin envoltorios triviales de una línea, salvo que encapsulen una constante de configuración o un contrato de seguridad (caso de
hashPassword/verifyPassword).
UI: shadcn/ui sobre Base UI, no Radix
- La composición usa la prop
render, noasChild:<Button render={<Link href="/x" />}>. - Si el
renderproduce un<a>(o cualquier no-<button>), agregarnativeButton={false}o Base UI emite error en consola. - Los archivos de
src/components/ui/**los genera el CLI de shadcn. Se pueden editar, pero no reformatear por gusto. - Componentes compartidos propios (
data-table,delete-button,field-error,submit-button) viven ensrc/components/, sin subcarpetaui. - Server Components por defecto.
"use client"solo donde hay hooks o estado: formularios, diálogos, links conusePathname.
Formularios
- Un formulario por entidad para alta y otro para edición; el de edición recibe la acción ya ligada desde el Server Component padre:
actualizarXAction.bind(null, id). - Errores inline con
<FieldError name="campo" errors={errors} />. - Botón de envío con
<SubmitButton label="…" pendingLabel="…" />(usauseFormStatus). precioVentanunca es un campo editable: se calcula al guardar.
API REST
Todo handler empieza igual:
const auth = await requireApiAuth(request);
if (auth instanceof NextResponse) return auth; // 401 ya construido
Códigos: 200 lectura, 201 creación, 204 eliminación, 400 validación ({ errors }), 401 token inválido/ausente, 404 id inexistente. Los usuarios se devuelven sin password.
Trampas conocidas
- No llamar
signIn()conredirectTodesde una Server Action. Con Next 16.3.5 + NextAuth v5 beta la acción queda colgada para siempre. El login usasignIndenext-auth/reactconredirect: falsey luegorouter.push. src/proxy.tsdebe exportar una función declarada (export default function proxy(...)). Un re-export desestructurado (export const { auth: proxy } = …) hace que Next lo rechace en el chequeo estático.- Argon2 y Prisma no corren en Edge.
auth.config.tsqueda libre de ambos porquesrc/proxy.tslo importa; los providers viven enauth.ts(runtime Node). /uploads/placeholder.svges compartido: nunca se borra al eliminar un producto. Comparar contraPLACEHOLDER_IMAGENantes de llamar aeliminarImagenProducto.next/imagese usa conunoptimized: evita depender desharppara miniaturas de un backoffice.
Base de datos
- Cambios de esquema: editar
prisma/schema.prismay corrernpx prisma migrate dev --name <descripcion>. Nunca editar a mano una migración ya aplicada. prisma migrate resetes destructivo: pide confirmación explícita al usuario antes de ejecutarlo. Para recrear una base ausente bastanpx prisma migrate deploy.- El seed (
npm run db:seed) es idempotente (usaupsert).
Verificación antes de entregar
En este orden, y sin saltarse pasos:
npm run build— debe terminar sin errores de TypeScript.npm run lint.- Flujo real en navegador para cambios de UI: login, alta, edición, borrado.
curlpara cambios de API, incluyendo los casos401y400.
Los scripts de comprobación de un solo uso van a scripts/ y se borran cuando la verificación pasa; no se entregan.
Convención de Git
<tipo>(<alcance opcional>): <descripción en imperativo, minúscula, sin punto final>
<cuerpo opcional: qué y por qué, no cómo>
<pie opcional: BREAKING CHANGE: …>
Tipos permitidos
| Tipo | Uso |
|---|---|
feat |
Funcionalidad nueva visible para el usuario o la API |
fix |
Corrección de un defecto |
refactor |
Reestructuración sin cambio de comportamiento |
perf |
Mejora de rendimiento |
docs |
Documentación (README.md, docs/**, AGENTS.md) |
style |
Formato, sin efecto en lógica |
test |
Pruebas |
build |
Dependencias, migraciones de Prisma, configuración de build |
chore |
Tareas de mantenimiento, scaffolding |
revert |
Reversión de un commit anterior |
Alcances habituales
usuarios, productos, clientes, auth, api, backoffice, db, ui, docs.
Ejemplos
feat(productos): calcular precio de venta con IVA al guardar
fix(api): limpiar la imagen del producto al eliminarlo por DELETE
refactor(productos): centralizar la limpieza de imagen en el modelo
build(db): agregar indice unico a productos.sku
docs(readme): documentar el flujo de token de la API
Reglas
- Descripción en español, imperativo, ≤ 72 caracteres, sin punto final.
- Un commit = un cambio coherente. No mezclar una corrección con un refactor.
- El cuerpo explica el por qué cuando la razón no es obvia; el diff ya muestra el qué.
- Nunca commitear
.env,.env.local,data/*.dbni archivos depublic/uploads/(exceptoplaceholder.svg). - Nunca commitear código que no compile:
npm run buildantes del commit.