Webs y apps

Webs y aplicaciones móviles a medida

Diseñamos y programamos sitios web rápidos y aplicaciones para iOS y Android. Sin plantillas y sin ataduras: el código, el dominio y las cuentas quedan a tu nombre.

Qué construimos

Sitios web

Webs corporativas y páginas de aterrizaje que cargan al instante, preparadas para posicionar en Google y para funcionar en varios idiomas desde el primer día.

Aplicaciones móviles

iOS y Android desde una sola base de código: cada función se programa una vez y funciona en ambos sistemas. Publicación en App Store y Google Play incluida.

Backend e integraciones

Base de datos, cuentas de usuario, roles y permisos, cobros con Stripe, envío de correos, notificaciones push y conexión con servicios externos por API.

Soporte posterior

Un proyecto no termina el día del lanzamiento. Nos quedamos: corregimos, medimos, añadimos funciones y mantenemos la infraestructura.

Cómo trabajamos

  1. Primero el problema

    Antes de escribir una línea entendemos qué proceso te come tiempo o dinero. A veces la respuesta no es una aplicación, y lo decimos.

  2. Piezas que ya funcionan

    No reinventamos autenticación, pagos ni notificaciones. Usamos herramientas probadas y dedicamos el tiempo a lo que es único de tu negocio.

  3. Todo queda a tu nombre

    Código, dominio, hosting y base de datos son tuyos. Puedes llevártelo a otro equipo cuando quieras, sin pedirnos permiso.

Lo que ya hemos construido

Webs, sistemas web y apps para iOS y Android: trabajos para clientes y productos propios. Cada uno con su caso detallado: el problema, qué hay dentro, las pantallas y las tecnologías.

Proyectos

Ingeniería

El código con el que funciona esta web

Fragmentos tal cual están en el repositorio: el formulario, la firma de contratos, el estado de las reparaciones y el horario del taller. Lo recortado está marcado con «// …».

Cloudflare Workerssrc/worker.js
if (form.company) return json({ ok: true });

const name = String(form.name ?? '').trim().slice(0, 200);
const contact = String(form.contact ?? '').trim().slice(0, 200);
const about = String(form.about ?? '').trim().slice(0, MAX_LEN);

if (!name || !contact || !about || form.consent !== true) {
  return json({ error: 'validation' }, 400);
}

if (env.TURNSTILE_SECRET) {
  const ip = request.headers.get('cf-connecting-ip');
  const ok = await verifyTurnstile(env.TURNSTILE_SECRET, form.turnstile, ip);
  if (!ok) return json({ error: 'captcha' }, 400);
}

Trampa para bots y comprobación de Turnstile: a tu correo llegan solicitudes de personas y el spam se descarta en silencio.

Cloudflare Workerssrc/contracts.js
export async function sha256Hex(text) {
  return toHex(await crypto.subtle.digest('SHA-256', enc.encode(text)));
}
// …
const now = new Date().toISOString();
await env.DB.prepare(
  `update contracts
      set status = 'signed', signed_at = ?, signer_name = ?, signer_image = ?,
          signer_ip = ?, signer_ua = ?, signed_hash = ?,
          signer_phone = ?, signer_address = ?
    where id = ? and status <> 'signed'`,
)
  .bind(
    // …
    await sha256Hex(row.body),
    // …
  )
  .run();

Registramos la hora, la IP, el dispositivo y el SHA-256 del texto que la persona vio al firmar: la firma se puede demostrar y el documento no se puede cambiar.

Cloudflare Workerssrc/orders.js
const status = ALL_STATUSES.includes(b.status) ? b.status : row.status;
const now = new Date().toISOString();
// …
if (status !== row.status || clean(b.comment, 500)) {
  await addEvent(env, id, status, clean(b.comment, 500), now);
}

let mailed = false;
if (status === 'ready' && row.status !== 'ready') {
  mailed = await mailOrder(env, 'ready', {
    email: row.client_email,
    lang: row.lang,
    number: row.number,
    device: row.device,
    url: new URL(`/o/${id}`, SITE.url).href,
  });
}

Cada cambio de estado queda en el historial que el cliente ve en su enlace, y el correo sale una sola vez: cuando la reparación está lista.

navegadorsrc/scripts/shop-status.js
function canary(d) {
  const f = new Intl.DateTimeFormat('en-CA', {
    timeZone: 'Atlantic/Canary', year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', hourCycle: 'h23', weekday: 'short',
  });
  const p = Object.fromEntries(f.formatToParts(d).map((x) => [x.type, x.value]));
  const dow = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }[p.weekday];
  return { date: `${p.year}-${p.month}-${p.day}`, dow, mins: Number(p.hour) * 60 + Number(p.minute) };
}
const toMins = (hhmm) => Number(hhmm.slice(0, 2)) * 60 + Number(hhmm.slice(3, 5));
const workday = (c) => (HOURS[c.dow] && !HOLIDAYS.has(c.date) ? HOURS[c.dow] : null);

export function shopStatus(now = new Date()) {
  const c = canary(now);
  const h = workday(c);
  if (h && c.mins >= toMins(h[0]) && c.mins < toMins(h[1])) return { open: true, closes: h[1] };
  // …
}

La web sabe sola si el taller está abierto: hora de Tenerife con horario de verano y festivos, del mismo origen que los datos para Google.

El stack de nuestros productos

Solo lo que de verdad mueve HostBase, Hourgrid, Mansaria y esta web. Nada puesto para aparentar.

  • Astro, Web
  • Next.js, Web
  • TypeScript, Web
  • Tailwind CSS, Web
  • Canvas, Web
  • React Native, Móvil
  • Expo, Móvil
  • PostgreSQL, Datos
  • Supabase, Datos
  • Cloudflare D1, Datos
  • Cloudflare R2, Datos
  • Workers KV, Datos
  • Cloudflare Workers, Servidor
  • Cron Triggers, Servidor
  • Netlify, Servidor
  • Row Level Security, Seguridad
  • Turnstile, Seguridad
  • Web Crypto, Seguridad
  • Stripe, Pagos
  • Verifactu, Fiscal
  • Brevo, Correo
  • Anthropic, IA
  • Groq, IA
  • Sentry, Monitorización

¿Ya sabes lo que necesitas? Marca las tareas, el stack y la forma de trabajar: la solicitud se arma sola.

Arma tu proyecto en el configurador

¿Tienes una idea, o un proceso que te come el día?

Cuéntanoslo por WhatsApp. La primera conversación es gratuita y sirve para ver si merece la pena hacerlo.

WhatsApp