KiraWebs

Next.jsNotion APIRedis

Kirawebs.com es un sitio de marketing para una agencia de desarrollo web, construido alrededor de un formulario interactivo de múltiples pasos que permite a un cliente potencial simular el costo de su sitio web antes de hablar con nadie — convirtiendo el "¿cuánto costaría esto?" en una respuesta autoservicio en vez de una solicitud de cotización.

Arquitectura y Stack Tecnológico

Arquitectura Central

  • Framework: Next.js (App Router) + React
  • UI: Tailwind CSS + componentes shadcn/ui
  • Validación: Zod en cada envío del formulario de contacto
  • Rate limiting: Redis, limitador de ventana fija (3 mensajes / minuto por IP)
  • Almacenamiento de datos: Notion API — los envíos de contacto se escriben directo en una base de datos de Notion, sin necesidad de un backend aparte
  • Despliegue: Appwrite

Flujo del Formulario de Contacto

Todo el camino del envío corre dentro de una sola Server Action: primero valida, segundo aplica rate limit, tercero persiste — cada paso puede rechazar temprano con un mensaje específico y visible para el usuario en vez de un error genérico.

Características Principales

Características de un Vistazo

Simulador Interactivo de Costo del Sitio Web

Un formulario guiado de múltiples pasos — plantillas, secciones, servicios y luego un resumen — lleva al visitante a configurar su sitio y devuelve una estimación de costo en vivo, reemplazando la solicitud de cotización de ida y vuelta con algo que el visitante puede explorar por su cuenta.

Image

Image

Formulario de Contacto con Validación y Rate Limiting

El correo y el mensaje se validan con Zod tanto en cliente como en servidor antes de escribir nada; un limitador de ventana fija en Redis limita los envíos a 3 por minuto por IP, así que el mismo patrón de abuso que llenaría un buzón recibe en cambio una respuesta clara de "espera un momento".

Image

Image

Destacados Técnicos

Notion como base de datos del formulario de contacto

En vez de levantar una base de datos para un puñado de envíos de contacto, sendEmail escribe cada uno directo en una base de datos de Notion vía la API de Notion — cero infraestructura para lo que genuinamente es un formulario de bajo volumen.

const response = await notion.pages.create({
  parent: { database_id: DATABASE_IDS.contact! },
  properties: {
    email: { title: [{ text: { content: email } }] },
    description: { rich_text: [{ text: { content: description } }] },
  },
})

Rate limiting verificado antes que la validación

El limitador de ventana fija en Redis corre primero en la Server Action, antes de que Zod siquiera mire el payload — un cliente abusivo es rechazado antes de que la app gaste trabajo parseando o validando su input.

const ratelimit = new Ratelimit({
  redis: redis,
  limiter: Ratelimit.fixedWindow(3, "60 s"),
})

export async function sendEmail(prevState: unknown, formData: FormData) {
  const clientIp = formData.get("clientIp") as string
  const result = await ratelimit.limit(clientIp)

  if (!result.success) {
    return {
      success: false,
      title: "Límite de envíos alcanzado",
      details: "Por favor, espera un momento antes de intentar nuevamente.",
    }
  }
  // ...validación con Zod, luego notion.pages.create
}

Cada rechazo tiene una razón específica y visible para el usuario

Rate-limited, input inválido y un fallo al escribir en Notion devuelven cada uno su propio par title + details en vez de un error genérico — el visitante siempre sabe exactamente qué corregir o esperar.

Estructura del Proyecto

src/
├── app/
│   ├── page.tsx                 # Sitio de marketing de una sola página
│   ├── layout.tsx
│   └── actions/
│       └── contact.ts           # Server Action: rate limit → Zod → escritura en Notion
├── components/
│   ├── home/
│   │   ├── hero-section.tsx
│   │   ├── services-section.tsx
│   │   ├── compare-section.tsx
│   │   ├── price-simulator-section.tsx
│   │   ├── price-simulator/      # Simulador de costo de múltiples pasos (plantillas → secciones → servicios → resumen)
│   │   ├── steps-section.tsx
│   │   ├── faq-section.tsx
│   │   └── contact-section.tsx
│   │       └── contact/
│   │           └── contact-form.tsx
│   ├── layout/                   # Header, footer
│   └── ui/                       # Primitivas de shadcn/ui

Impacto y Escalabilidad

  • Estimación de costo autoservicio: reemplaza las solicitudes manuales de cotización con un simulador instantáneo y explorable
  • Captura de datos sin infraestructura: los envíos de contacto llegan directo a Notion, sin base de datos que aprovisionar o mantener
  • Resistente al abuso por diseño: el rate limiting con Redis protege el formulario sin necesitar un servicio aparte
  • Desplegado y en vivo: corriendo en Appwrite en kirawebs.appwrite.network

Notas

Construido con Next.js, Tailwind CSS, Redis y la Notion API, desplegado en Appwrite. El código es público en GitHub.

🖼️ IMAGE PLACEHOLDER — vista de la base de datos de Notion mostrando los envíos de contacto almacenados