Aplicación a medida desarrollada para Ainoa's Cakes, pastelería de pasteles personalizados y temáticos en El Empalme, Ecuador. Centraliza la operación diaria del negocio (pedidos, clientes, productos e ingresos) y expone un catálogo público para que los clientes descubran productos y coticen por WhatsApp.
ACakes es el software interno que usa Ainoa's Cakes para operar su taller de pastelería a diario. No es un menú de precios fijos: cada pastel se cotiza y confecciona a pedido, así que el sistema está construido alrededor de ese flujo real de negocio.
La app tiene dos capas:
| Capa | Para quién | Rutas |
|---|---|---|
| Panel administrativo | La dueña del negocio, desde el celular | /panel, /panel/clientes, /panel/productos, /panel/ingresos, /panel/cuenta |
| Catálogo público | Clientes finales | /, /catalogo, /producto/:id, /mis-favoritos |
-
Los precios nunca se muestran en el catálogo público: la cotización siempre ocurre por WhatsApp.
-
El catálogo se organiza por temática/ocasión (cumpleaños, quinceañeras, infantil, etc.), no por categoría genérica de producto.
-
Cobertura y entrega: El Empalme, Ecuador.
-
Demo en producción: ainoascakes.netlify.app
-
Repositorio:
github.com/eduaardok/acakes-app
Panel de Pedidos — vista semanal mobile-first, con estados por pedido (Borrador, Confirmado, En proceso, Listo, Entregado) y acceso rápido a crear un nuevo pedido.
- Backend: Node.js + Express + TypeScript API REST simple, predecible y rápida de iterar. TypeScript reduce bugs en cambios de reglas de negocio (transiciones de estado, filtros de ingresos, etc.).
- ORM: Prisma Tipado end-to-end en la capa de datos y migraciones controladas. Modelado claro de relaciones (Cliente → Pedidos → Observaciones, Producto → Categoría).
- Base de datos: PostgreSQL (Supabase) Postgres por consistencia/transacciones y queries estructuradas. Supabase como Postgres gestionado, con Storage para las imágenes de productos y pedidos.
- Frontend: React + Vite + React Router React para UI por estados y componentes reutilizables. Vite para DX rápida. Router para separar panel administrativo y catálogo público sin complejidad extra.
- UI: Tailwind CSS Iteración rápida en UI mobile-first sin depender de librerías de componentes, manteniendo consistencia visual con la identidad de marca.
- Auth: JWT propio + bcrypt Control explícito del flujo (login/logout, expiración, protección de rutas) sin vendor lock-in, con auth separada para panel y clientes.
Monorepo simple con dos apps:
ACakes/
├── backend/
└── frontend/
backend/src/
├── routes/ → auth, clientes, pedidos, producto, categorias, usuariosCliente, public
├── controllers/ → auth, clientes, pedidos, producto, categoria, catalogo, interacciones, clienteAuth, pedidoImagenes
├── middleware/ → auth.middleware.ts, auth.cliente.middleware.ts, upload.middleware.ts
├── lib/ → prisma.ts (singleton), transiciones.ts, supabaseStorage.ts, mailer.ts, publicCache.ts
└── index.ts → rutas públicas (/health, /auth, /public) y protegidas (/clientes, /pedidos, /productos)
frontend/src/
├── pages/ → Dashboard, Clientes, Productos, Categorias, Ingresos, Cuenta, DetallePedido, DetalleCliente...
├── public/ → Landing, Catalogo, ProductoDetalle, login/registro de cliente, favoritos
├── components/ → PedidoCard, BuscadorCliente, BottomNav, Layout, PrivateRoute...
├── hooks/ → usePedidosHoy, usePedido, useClientes, useCliente, useIngresos, useCatalogo...
└── lib/ → api.ts (wrapper fetch con JWT + manejo de 401)
verbatimModuleSyntax— Fuerza imports consistentes (import type) y evita ambigüedades entre tipos/valores en TS, especialmente en frontend.- Singleton de Prisma — Una única instancia del cliente Prisma para evitar múltiples conexiones en desarrollo/hot reload.
- Debounce en el buscador de clientes — Reduce carga de requests y mejora la UX en mobile; exige mínimo de caracteres para evitar ruido.
- Skeletons en cargas — Mejor percepción de performance y layout estable vs. spinners genéricos.
- JWT propio — Control total sobre expiración, payload y manejo de 401 (redirección al login) sin dependencias "mágicas".
- Fechas con
T00:00:00— Normalización al construir rangos (inicio/fin de día) para minimizar desfasajes por zona horaria al filtrar ingresos. - Sin precios en el catálogo público — Regla de negocio aplicada de forma consistente en todo el código público; la cotización siempre pasa por WhatsApp.
- Node.js (LTS recomendado)
- npm
- Una base Postgres accesible (por ejemplo un proyecto en Supabase)
Crear un archivo backend/.env con valores reales:
# Backend
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DB?schema=public"
JWT_SECRET="cambia-esto-por-un-secreto-largo"
PORT=3000
CORS_ORIGIN="http://localhost:5173"Crear un archivo frontend/.env:
# Frontend
VITE_API_URL="http://localhost:3000"Backend:
cd backend
npm install
npx prisma generate
# opcional si usas migraciones en tu entorno local:
# npx prisma migrate dev
npm run devFrontend:
cd frontend
npm install
npm run devNotas operativas:
- Si el backend devuelve 401, el frontend maneja el caso y redirige al login.
- Si tu base devuelve decimales como string (común en ORMs/PG), el frontend normaliza para mostrar importes correctamente.
Arquitectura de deploy en producción:
- Supabase — PostgreSQL gestionado (
DATABASE_URL) y Storage para imágenes. - Railway — backend (API Express)
- Variables:
DATABASE_URL,JWT_SECRET,CORS_ORIGIN(apuntando al dominio de Netlify),PORT(si aplica). - Comandos: build
npm run buildy startnpm run start.
- Variables:
- Netlify — frontend (Vite) → ainoascakes.netlify.app
- Variable:
VITE_API_URLapuntando al dominio del backend en Railway. - Build:
npm run build, publish:dist/.
- Variable:

