Lo Que Construir Software para Pequeñas Empresas Me Enseñó Sobre Ingeniería
Resumen
Llegué a un taller de detallado automotriz con un documento de arquitectura de microservicios de 12 páginas y me dejaron por el piso. Construir para pequeñas empresas te obliga a entregar rápido, elegir tecnología aburrida, y aprender que la mejor ingeniería no se trata de complejidad — se trata de resolver problemas reales para personas reales que te van a escribir a las 2 AM si algo se rompe. Estas lecciones me hicieron mejor ingeniero a cualquier escala.
Llegué a mi primera reunión con un cliente de pequeña empresa con un documento de arquitectura técnica de 12 páginas. Microservicios. Kubernetes. Colas de mensajes basadas en eventos. Todo el paquete completo. El tipo tiene un negocio de detallado automotriz que opera desde su camioneta. Me miró, miró el documento, me volvió a mirar, y dijo "solo necesito que la gente reserve en línea, hermano."
Esa fue la lección de producto más importante de mi carrera.
Desde entonces, he dedicado una parte significativa de mi carrera a construir software para pequeñas empresas. No startups respaldadas por capital de riesgo con equipos de ingeniería y mesas de ping pong. Pequeñas empresas reales — un taller de detallado automotriz, un servicio de restauración de imágenes, una empresa de limpieza. El tipo de negocios donde el dueño contesta el teléfono, hace el trabajo, factura al cliente, y de alguna manera también necesita gestionar un sitio web y un sistema de reservas en medio de todo eso.
Y voy a ser honesto: este trabajo me enseñó más sobre ingeniería que cualquier proyecto empresarial. Y no fue ni siquiera reñido.
La Brecha Entre Enterprise y "¿Mis 50 Clientes Pueden Reservar?"
En la ingeniería enterprise, nos obsesionamos con cosas de las que los dueños de pequeñas empresas literalmente nunca han escuchado. Y lo digo en serio — una vez intenté explicarle "escalabilidad horizontal" a la dueña de un servicio de limpieza y me dijo: "Mijo, tengo 40 clientes. No necesito nada horizontal. Necesito vertical — como en, necesito pararme derecha después de fregar pisos todo el día."
Buen punto.
┌─────────────────────────────────────────────────────────────────┐
│ Enterprise vs Small Business Priorities │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Enterprise Engineering Small Business Reality │
│ ───────────────────── ───────────────────── │
│ Horizontal scalability "Can my 50 customers book?" │
│ Microservices architecture "Does the website load?" │
│ CI/CD pipelines "Can I update the prices?" │
│ 99.99% uptime SLAs "Is my site down right now?" │
│ A/B testing frameworks "Do people actually call?" │
│ Distributed tracing "Why didn't I get the email?" │
│ Kubernetes orchestration "How much does hosting cost?" │
│ │
└─────────────────────────────────────────────────────────────────┘
Cuando empecé a construir para pequeñas empresas, traje toda mi mentalidad enterprise como un niño que lleva una calculadora científica a un puesto de limonada. Propuse arquitecturas con servicios separados, colas de mensajes y orquestación de contenedores. El dueño del taller de detallado automotriz me miró como si le hubiera sugerido lanzar su negocio a Marte.
Quería tres cosas: un sitio web que apareciera en Google, una forma para que los clientes reservaran citas, y una forma de recibir pagos.
Eso es todo. Tres cosas. Y honestamente, servir bien esas tres necesidades es más difícil de lo que suena. Mucho más difícil. Porque no hay donde esconderse cuando el alcance es tan pequeño — cada borde áspero es visible, cada página lenta le cuesta a una persona real un cliente real.
Entregar Rápido vs Entregar Bien (No Son Opuestos, Lo Prometo)
Esta es la tensión que todo ingeniero freelance siente en los huesos: quieres construir las cosas correctamente, pero el cliente lo necesitaba para ayer, y el presupuesto es... digamos "optimista." Una vez le pasé mi tarifa a un dueño de negocio y se rió tan fuerte que pensé que iba a necesitar atención médica.
Solía pensar que "entregar rápido" y "entregar bien" eran opuestos. Como que tenías que elegir uno. Resulta que son ejes completamente diferentes, y el punto dulce es totalmente alcanzable — solo tienes que tragarte el orgullo en un par de cosas.
┌─────────────────────────────────────────────────────────────────┐
│ The Shipping Matrix │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Fast Slow │
│ Right ┌──────────────────┬──────────────────┐ │
│ │ Sweet spot: │ Enterprise: │ │
│ │ Boring tech, │ Thorough but │ │
│ │ clear scope, │ often over- │ │
│ │ proven patterns │ engineered │ │
│ ├──────────────────┼──────────────────┤ │
│ Wrong │ Prototype: │ Worst case: │ │
│ │ Quick hack that │ Slow AND broken │ │
│ │ becomes prod │ (analysis │ │
│ │ (tech debt bomb) │ paralysis) │ │
│ └──────────────────┴──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
El punto dulce — rápido Y bien — viene de elegir tecnología aburrida, reutilizar patrones probados, y recortar alcance sin piedad. Énfasis en sin piedad. Estoy hablando de niveles de "esta funcionalidad parece importante pero la vamos a cortar de todas formas."
Para Shining Image, un servicio de restauración y digitalización de fotos, necesitaba un sitio con un catálogo de servicios, una galería de antes/después, y un formulario de contacto. ¿Mi primer instinto? Construir un CMS personalizado. Porque claro que sí. Soy ingeniero — vemos un clavo y buscamos una fábrica de clavos. En su lugar, me forcé a usar Next.js con páginas estáticas, Tailwind para los estilos, y un formulario de contacto simple que enviaba correos vía Resend. Desplegado en Vercel. Listo en menos de una semana.
¿Era la arquitectura más elegante? No. ¿Me hizo sentir ingenioso? Tampoco. ¿Funcionó perfectamente para el negocio y sigue funcionando hoy con cero mantenimiento? Sí. Y ese es el punto.
El 80/20 del Software para Pequeñas Empresas
El 80% de las necesidades de software para pequeñas empresas se resuelven con: un sitio web rápido, una forma de recopilar información del cliente, una forma de aceptar pagos, y notificaciones automáticas por correo electrónico. Construye bien esas cuatro cosas y habrás cubierto la mayoría de los casos. Ya sé que suena aburrido. Es porque lo es. Abraza lo aburrido.
Eligiendo Tecnología Aburrida (Una Carta de Amor)
Soy un firme defensor de la tecnología aburrida en general, pero para proyectos de pequeñas empresas no es solo una preferencia — es una responsabilidad. Y lo aprendí por las malas.
Cuando entregas un proyecto a un dueño de pequeña empresa, no hay un equipo de ingeniería para mantenerlo. No hay rotación de guardia. No hay un canal de Slack donde alguien va a hacer triage del problema. Si algo se rompe a las 2 AM, el dueño del negocio te va a escribir a ti personalmente. (Pregúntame cómo lo sé. Pregúntame por los mensajes que me han llegado a las 2 AM. Mejor no — mi terapeuta dice que tengo que dejar de revivir esas experiencias.)
Este es mi stack tecnológico para pequeñas empresas y por qué cada elección existe:
// The "Boring Stack" for Small Business Projects
const boringStack = {
framework: "Next.js", // Large community, great docs, Vercel hosting
styling: "Tailwind CSS", // Utility-first, no CSS-in-JS runtime issues
database: "PostgreSQL", // Rock solid, free tier on Supabase/Neon
orm: "Prisma", // Type-safe, readable queries, easy migrations
payments: "Stripe", // Industry standard, handles PCI compliance
email: "Resend", // Simple API, reliable delivery
hosting: "Vercel", // Zero-config deploys, generous free tier
auth: "NextAuth.js", // When needed — many small biz apps don't need auth
monitoring: "Vercel Analytics", // Built-in, no extra setup
};
// What I explicitly AVOID for small business projects:
const notBoring = {
avoid: [
"Microservices", // One service is enough
"Kubernetes", // Way too much operational overhead
"Custom auth", // Security liability
"NoSQL databases", // PostgreSQL handles everything
"GraphQL", // REST is simpler to debug
"Monorepos with Turborepo", // It's one app
"Redis", // PostgreSQL can handle the caching
"Message queues", // Direct function calls work fine
],
};Cada uno de los ítems en la lista de "avoid" es una tecnología que yo personalmente he propuesto para un proyecto de pequeña empresa en algún momento. Cada una fue un error, o lo habría sido si alguien no me hubiera disuadido. Una vez casi monté un Redis cache para una app que tenía 12 usuarios concurrentes. Doce. Mi instancia de PostgreSQL no estaba haciendo literalmente nada. Estaba aburrida. Estaba rogando por queries.
La Tecnología Es una Responsabilidad, No un Activo
Cada elección tecnológica es una carga de mantenimiento. Para empresas grandes, la carga se distribuye entre un equipo. Para pequeñas empresas, la carga recae en una sola persona — tú — potencialmente por años. Elige en consecuencia. Tu yo del futuro te va a agradecer o te va a maldecir. No hay punto medio.
Construyendo un Sistema de Reservas Real (También Conocido Como "Suena Simple, No Lo Es")
Para AutoSpa, un servicio móvil de detallado automotriz, la necesidad central era un sistema de reservas. Los clientes necesitaban elegir un servicio, seleccionar fecha y hora, y pagar. ¿Suena simple, verdad?
Ja. Jaja. No.
Pensé que me tomaría una semana. Me tomó tres. ¡Y eso que ya había construido sistemas de reservas antes! (De hecho, no era una funcionalidad simple.) Esto es lo que un sistema de reservas realmente implica cuando te sientas a pensarlo:
// The booking data model — deceptively complex
interface BookingSystem {
// Service catalog
services: {
id: string;
name: string; // "Full Detail", "Interior Only", etc.
duration: number; // in minutes
price: number;
description: string;
addOns: AddOn[]; // "Ceramic Coating", "Pet Hair Removal"
}[];
// Availability management
availability: {
businessHours: DaySchedule[]; // Mon-Sat 8am-6pm
bufferTime: number; // 30 min between appointments
maxBookingsPerDay: number; // Capacity limit
blockedDates: Date[]; // Holidays, vacation
travelTime: number; // Mobile service — need transit time
};
// The booking itself
booking: {
customer: CustomerInfo;
service: string;
addOns: string[];
datetime: Date;
location: Address; // Mobile service goes to the customer
vehicle: VehicleInfo; // Make, model, size affects pricing
status: "pending" | "confirmed" | "completed" | "cancelled";
payment: PaymentInfo;
remindersSent: Date[];
};
}¿Ves ese campo travelTime en la configuración de disponibilidad? Esa sola línea representa aproximadamente cuatro horas de mí mirando una pizarra pensando, "A ver, si él está en Miami Beach a las 2 PM y la siguiente cita es en Coral Gables a las 3 PM, y es viernes..." Los negocios móviles agregan una capa de complejidad de programación que te hace explotar la cabeza.
La parte difícil no es el modelo de datos. Son los casos extremos — esas cosas que nadie menciona en el tutorial:
- ¿Qué pasa cuando dos personas intentan reservar el mismo horario? (Esto pasó. El primer día. Por supuesto que pasó.)
- ¿Cómo manejas las cancelaciones y reembolsos? (La política cambió tres veces en el primer mes.)
- ¿Qué pasa con el tiempo de traslado entre citas móviles?
- ¿Cómo envías recordatorios sin molestar a la gente?
- ¿Qué pasa si el cliente no se presenta? (Spoiler: lo va a hacer.)
// Slot availability — must account for existing bookings + buffer + travel
async function getAvailableSlots(date: Date, serviceId: string): Promise<TimeSlot[]> {
const service = await getService(serviceId);
const daySchedule = getDaySchedule(date);
const existingBookings = await getBookingsForDate(date);
const slots: TimeSlot[] = [];
let currentTime = daySchedule.start;
while (currentTime + service.duration <= daySchedule.end) {
const slotEnd = currentTime + service.duration;
const hasConflict = existingBookings.some((booking) => {
const bookingStart = booking.startTime;
const bookingEnd = booking.endTime + BUFFER_MINUTES + TRAVEL_MINUTES;
return currentTime < bookingEnd && slotEnd > bookingStart;
});
if (!hasConflict) {
slots.push({
start: currentTime,
end: slotEnd,
available: true,
});
}
currentTime += SLOT_INCREMENT; // 30-minute increments
}
return slots;
}Aprendí que las race conditions en los sistemas de reservas son muy reales. No de forma teórica de ciencias de la computación — de la forma "dos humanos reales quieren el horario de las 10 AM del sábado y los dos están haciendo clic en Reservar Ahora ahorita mismo." La solución es honestamente más simple de lo que pensarías: bloqueo a nivel de base de datos. No intentes ser ingenioso. Deja que PostgreSQL haga lo que PostgreSQL hace mejor.
// Optimistic locking with Prisma — prevent double bookings
async function createBooking(data: BookingInput) {
return await prisma.$transaction(async (tx) => {
// Check if slot is still available inside the transaction
const conflicting = await tx.booking.findFirst({
where: {
date: data.date,
startTime: { lte: data.endTime },
endTime: { gte: data.startTime },
status: { not: "cancelled" },
},
});
if (conflicting) {
throw new Error("This time slot is no longer available");
}
// Create the booking
const booking = await tx.booking.create({ data });
return booking;
});
}Lancé la primera versión sin el wrapper de transacción. "¿Cuáles son las probabilidades de que dos personas reserven al mismo tiempo?" me dije a mí mismo, como un tonto. Las probabilidades aparentemente eran del 100%, porque pasó literalmente el primer sábado que el sistema estuvo en vivo. Un cliente llegó y encontró a alguien más recibiendo el detallado de su carro. El dueño me llamó. No fue una conversación agradable.
Transacciones. Siempre transacciones. Lección aprendida. (De hecho, no estuvo todo bien.)
Manejando Pagos con Stripe (Lo Único Que Hice Bien Desde el Día Uno)
Cada app de pequeña empresa que he construido involucra pagos. Stripe es la respuesta, siempre. Moriré en esta colina.
No porque sea la más barata (no lo es — ese 2.9% + 30 centavos se acumula y tu cliente TE VA a preguntar al respecto). No porque la API sea perfecta (aunque está bastante cerca — quien diseñó su documentación de API merece un aumento y unas vacaciones). Porque Stripe maneja el cumplimiento PCI, la detección de fraude, las disputas, y los reportes fiscales — problemas que absoluta y definitivamente no quieres resolver tú mismo. Tengo un amigo que intentó construir un sistema de pagos personalizado una vez. No hablamos de eso. Él tampoco habla de eso. Solo se queda mirando al vacío a veces.
// Creating a Stripe Checkout Session for a booking
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function createCheckoutSession(booking: Booking) {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: booking.service.name,
description: `${booking.service.name} - ${format(booking.date, "MMM d, yyyy")} at ${booking.time}`,
},
unit_amount: booking.totalPrice * 100, // Stripe uses cents
},
quantity: 1,
},
],
mode: "payment",
success_url: `${process.env.NEXT_PUBLIC_URL}/booking/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/booking/cancel`,
metadata: {
bookingId: booking.id,
customerEmail: booking.customer.email,
},
});
return session;
}El handler de webhooks es donde las cosas se ponen picantes. Stripe envía eventos de forma asíncrona, y necesitas manejarlos de forma idempotente. "Idempotente" es una palabra que uso para sonar inteligente, pero básicamente significa "si Stripe te manda el mismo evento dos veces porque internet hizo hipo, no le cobres al cliente dos veces." Te sorprendería cuánta gente se equivoca en esto. (Yo me equivoqué en esto.)
// Stripe webhook handler — must be idempotent
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get("stripe-signature")!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return new Response("Invalid signature", { status: 400 });
}
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
const bookingId = session.metadata?.bookingId;
if (!bookingId) break;
// Idempotency: check if already processed
const booking = await prisma.booking.findUnique({ where: { id: bookingId } });
if (booking?.status === "confirmed") break; // Already processed
await prisma.booking.update({
where: { id: bookingId },
data: { status: "confirmed", stripeSessionId: session.id },
});
// Send confirmation email
await sendBookingConfirmation(booking);
// Send SMS reminder (scheduled for 24h before appointment)
await scheduleReminder(booking);
break;
}
case "charge.refunded": {
// Handle refund — update booking status
const charge = event.data.object as Stripe.Charge;
await handleRefund(charge);
break;
}
}
return new Response("OK", { status: 200 });
}¿Ves esa verificación de idempotencia — if (booking?.status === "confirmed") break? Ese pequeño if me salvó de un bug que habría enviado correos de confirmación duplicados. Lo descubrí durante las pruebas cuando recibí siete correos de confirmación por una sola reserva. Siete. La función de replay de webhooks del CLI de Stripe es una bendición y una maldición al mismo tiempo.
Pruebas de Webhooks de Stripe — Ahórrate Horas
Usa el CLI de Stripe para reenviar webhooks a tu servidor de desarrollo local: stripe listen --forward-to localhost:3000/api/webhooks/stripe. Esto ahorra horas de depuración comparado con desplegar para probar los flujos de webhooks. Una vez pasé un día entero desplegando a staging para depurar un problema de webhook. Un día entero. El CLI de Stripe lo habría encontrado en cinco minutos. Aprende de mi dolor.
Confiabilidad Sobre Funcionalidades (O: A Nadie Le Importan Tus Animaciones)
La lección más importante del trabajo con pequeñas empresas, y no puedo enfatizarlo lo suficiente: la confiabilidad supera a las funcionalidades cada vez. Cada. Una. De. Las. Veces.
A un dueño de pequeña empresa no le importa tu animación ingeniosa. No le importa tu patrón de UI innovador. No le importa que implementaste una máquina de estados realmente elegante. (Lo sé. A mí también me da tristeza.) Lo que le importa es:
- ¿El sitio web carga rápido? — Los sitios lentos pierden clientes. Dinero real. No "métricas de engagement" — dólares reales saliendo por la puerta.
- ¿Los clientes realmente pueden reservar/comprar? — Si el flujo de pago se rompe, pierden dinero. Y te van a llamar. A la hora de la cena. Un sábado.
- ¿Me notifican? — Necesitan saber cuándo llegan las reservas. Una notificación perdida es un cliente perdido.
- ¿Funciona en teléfonos? — La mayoría de sus clientes están en móvil. Si solo pruebas en tu monitor de 27 pulgadas, te va a ir mal.
Para Clear Choice, una empresa de servicios de limpieza, construí una calculadora de cotizaciones simple. La dueña quería una docena de campos para capturar cada detalle sobre el hogar del cliente. Tipo de mascota. Color de la alfombra. Número de ventanas. Si tenían una "cocina complicada" (sus palabras). Me opuse fuertemente. Lanzamos con cuatro campos: metros cuadrados, número de habitaciones, número de baños, y frecuencia. Eso fue suficiente para generar una cotización precisa el 90% del tiempo. El otro 10% recibía una llamada telefónica.
La dueña me odió durante como una semana. "¡¿Y la pregunta de las mascotas?!" me preguntaba constantemente.
// Simple quote calculator — covers 90% of cases
function calculateQuote(input: QuoteInput): Quote {
const baseRates: Record<string, number> = {
studio: 90,
"1bed": 120,
"2bed": 150,
"3bed": 190,
"4bed": 240,
};
const frequencyDiscount: Record<string, number> = {
once: 1.0,
biweekly: 0.9,
weekly: 0.8,
monthly: 0.95,
};
const base = baseRates[input.bedrooms] ?? 150;
const sqftAdjustment = Math.max(0, (input.sqft - 1000) / 500) * 25;
const bathroomAdjustment = Math.max(0, input.bathrooms - 1) * 20;
const discount = frequencyDiscount[input.frequency] ?? 1.0;
const total = Math.round((base + sqftAdjustment + bathroomAdjustment) * discount);
return {
estimate: total,
isEstimate: true,
message: `Estimated price: $${total} per visit`,
disclaimer: "Final price may vary based on home condition and specific needs.",
};
}Tres meses después, me dijo que el formulario simple de cotización fue la mejor decisión que tomamos. Los clientes realmente lo llenaban (porque tomaba 30 segundos en lugar de 5 minutos), y ella convertía más leads porque podía dar seguimiento antes de que cerraran la pestaña del navegador y se olvidaran de que querían un servicio de limpieza. A veces la mejor funcionalidad es la que no construyes.
El Lado del Negocio Te Hace Mejor Ingeniero (Te Guste o No)
Trabajar con clientes de pequeñas empresas te obliga a pensar en cosas que los ingenieros enterprise a menudo ignoran — o peor, activamente desestiman como "no es mi trabajo":
El costo importa. De verdad, importa. Cuando el presupuesto entero del cliente para software es $200/mes en hosting y herramientas, aprendes a ser recursivo rapidísimo. Descubres que el plan gratuito de Vercel maneja el 90% del tráfico de pequeñas empresas. Aprendes que la instancia gratuita de PostgreSQL de Supabase es más que suficiente para un negocio local. Dejas de levantar infraestructura "por si acaso" porque "por si acaso" cuesta $47/mes y el margen de ganancia del cliente es más delgado de lo que piensas.
La simplicidad es una funcionalidad. El panel de administración de AutoSpa tiene seis botones. No porque no pudiera construir más — créeme, tenía diseños. Diseños hermosos. Un dashboard con gráficas y analíticas y una herramienta de segmentación de clientes. Pero el dueño gestiona su negocio desde su teléfono entre citas mientras todavía tiene las manos mojadas de lavar carros. Cada botón extra es carga cognitiva que literalmente no puede permitirse. Seis botones. Es perfecto. Nunca he estado más orgulloso de algo tan simple.
Tú eres todo el equipo. No hay equipo de frontend, equipo de backend, equipo de DevOps, y equipo de diseño. Tú eres todos ellos. También eres QA, gestión de proyectos, y soporte al cliente. Suena terrible (y algunos días lo es), pero te obliga a ser pragmático con cada decisión porque eres tú quien tiene que vivir con todas ellas. Dejas de sobreingenierizar muy rápido cuando te das cuenta de que vas a ser tú depurando tus propias abstracciones "ingeniosas" a medianoche.
┌─────────────────────────────────────────────────────────────────┐
│ Skills Developed by Small Biz Work │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Technical Non-Technical │
│ ───────── ───────────── │
│ Full-stack ownership Requirements gathering │
│ Database design Client communication │
│ Payment integration Scope management │
│ DevOps / deployment Budget estimation │
│ Performance optimization Expectation setting │
│ SEO fundamentals Teaching non-technical users │
│ Email deliverability Long-term maintenance planning │
│ Mobile responsiveness Business model understanding │
│ │
└─────────────────────────────────────────────────────────────────┘
En Serio, Haz Esto
Si eres un ingeniero junior o de nivel medio buscando acelerar tu crecimiento, ve y construye algo real para una pequeña empresa. No me importa si les cobras $500 o lo haces gratis. La restricción de servir a un cliente real con un presupuesto real te enseña cosas que ningún tutorial, bootcamp, o proyecto personal jamás te enseñará. Vas a aprender más en un mes de "la página de reservas está rota y estoy perdiendo clientes" que en un año construyendo apps de tareas pendientes.
Lo Que Le Diría a Mi Yo del Pasado (Que Definitivamente No Me Haría Caso)
Si pudiera volver a cuando empecé a construir para pequeñas empresas, esto es lo que me diría. (Mi yo del pasado ignoraría la mayoría de esto, porque mi yo del pasado pensaba que era muy inteligente. No lo era.)
No sobreingenierices. Por favor. Te lo suplico. El taller de detallado automotriz no necesita una arquitectura de microservicios. Una sola app de Next.js con una base de datos PostgreSQL va a sobrevivir a la infraestructura de la mayoría de las startups. También va a sobrevivir al cluster de Kubernetes con el que estás fantaseando, porque los clusters de Kubernetes necesitan personas para operarlos y este negocio tiene una persona y es un tipo que se llama Carlos que es muy bueno haciendo que los carros brillen pero no tiene idea de qué es un pod.
Cobra por el mantenimiento. Construir la app es la mitad del trabajo. La otra mitad es mantenerla funcionando, actualizar dependencias, arreglar el caso extremo aleatorio que aparece seis meses después cuando alguien intenta reservar una cita para el 29 de febrero (pregúntame cómo lo sé). Incluye el mantenimiento en el contrato desde el día uno. Tu yo del futuro va a cobrar por este trabajo o lo va a hacer gratis. No hay tercera opción.
Documenta todo para personas no técnicas. Escribe una guía simple que diga "así se actualizan los precios" con capturas de pantalla. No un README técnico. No documentación de API. Capturas de pantalla. Con círculos rojos alrededor de los botones. Tu yo del futuro (y el de ellos) te lo agradecerá. Una vez me llamó un cliente diciendo "el sitio web está roto" y resultó que estaba intentando editar el sitio haciendo clic derecho en el navegador y seleccionando "Inspeccionar Elemento." La documentación importa.
Construye relaciones, no solo software. El dueño del taller de detallado automotriz me refirió a otros tres negocios. La dueña del servicio de limpieza le contó a todo su grupo de BNI sobre mí. Las comunidades de pequeñas empresas son muy unidas. El buen trabajo se acumula. Un solo cliente feliz vale más que cien publicaciones en LinkedIn sobre tu stack tecnológico.
Mide lo que importa. Para una pequeña empresa, las métricas que importan son: ¿sonó más el teléfono? ¿Aumentaron las reservas en línea? ¿El dueño pasó menos tiempo en trabajo administrativo? Si sí, tuviste éxito. Si no, tu arquitectura no importa. A nadie le importa tu Lighthouse score si el teléfono no está sonando. (Bueno, el Lighthouse score ayuda a que el teléfono suene, pero ya me entiendes.)
La Paradoja (O: Por Qué El Software "Simple" Es El Más Difícil)
Esta es la ironía, y me tomó años internalizarla completamente: construir software "simple" para pequeñas empresas es parte de la ingeniería más difícil que he hecho. De verdad. No porque los problemas técnicos sean complejos — un sistema de reservas no es investigación en sistemas distribuidos. Sino porque las restricciones son implacables.
No puedes esconderte detrás de la infraestructura. No puedes culpar a otro equipo. No puedes pedir más tiempo o presupuesto. No puedes decir "bueno, funciona en staging." Tienes que entregar algo que funcione, que una persona no técnica pueda usar sin llamarte, que corra de manera confiable con supervisión mínima, y que impacte directamente el sustento de alguien. El pago de su hipoteca está conectado a que tu código funcione. Esa es una presión diferente a "la velocidad del sprint bajó."
Esa presión produce mejores ingenieros. De verdad lo creo. He llevado cada lección de este trabajo a proyectos más grandes — el sesgo hacia la simplicidad, la obsesión con la confiabilidad, la disposición a recortar alcance sin piedad, y la comprensión de que el software existe para servir a las personas, no al revés.
La mejor ingeniería no se trata de construir el sistema más sofisticado. Se trata de construir el sistema correcto para las personas que lo van a usar. A veces ese sistema tiene seis botones y una base de datos PostgreSQL y está desplegado en un plan gratuito. Y a veces eso es exactamente perfecto.
El sistema de reservas de Carlos sigue funcionando, por cierto. Dos años y contando. Cero downtime. Me escribe a veces — no porque algo esté roto, sino para contarme que el negocio va bien. Esos son los mejores mensajes que he recibido a las 2 AM.
Frequently Asked Questions
No te pierdas nada
Artículos sobre IA, ingeniería y las lecciones que aprendo construyendo cosas. Sin spam, lo prometo.
Osvaldo Restrepo
Senior Full Stack AI & Software Engineer. Building production AI systems that solve real problems.