Desarrollo

Podoplus: toda la operación de la cadena en un sistema

Plataforma multi-sede para una cadena de 17 centros podológicos: agenda, punto de venta, facturación SUNAT, inventario y suscripciones, en web y móvil.

Podoplus: toda la operación de la cadena en un sistema

Problema

Una cadena de centros podológicos con 17 sedes en Lima y Callao operaba con la agenda, el cobro, el inventario y las suscripciones repartidos entre herramientas distintas. Con una sede eso se sostiene. Con diecisiete, nadie sabe cuánto se facturó hoy sin sumar a mano, el stock se descubre agotado cuando ya falta, y cada boleta emitida depende de que alguien recuerde qué serie corresponde a qué local.

El problema real no era "falta un software". Era que cada dato vivía en un sitio distinto y ninguno cuadraba con el otro.

Contexto

En un centro podológico conviven cosas que en otros negocios están separadas: una agenda clínica, un punto de venta con facturación electrónica, un almacén y un modelo de suscripciones de tratamientos recurrentes. Y encima, dos razones sociales distintas emitiendo comprobantes, cada una con sus propias series por sede.

Comprar cuatro herramientas y conectarlas después sale caro y se rompe por las costuras. Justo por donde pasa el dinero.

Arquitectura

Es un monorepo con cinco aplicaciones sobre una base de datos y un contrato de API compartidos:

AppQué es
apiBackend NestJS 10: REST /v1, WebSockets y los crons de mensajería
admin-webPanel administrativo React 18 + Vite, la app principal
admin-mobileApp del personal en Expo / React Native, distribución interna por EAS
workerJobs en segundo plano con BullMQ
customer-portalAutoservicio del paciente — hoy en modo demo

Debajo, PostgreSQL 15 con Prisma (unos 48 modelos y 26 migraciones) y Redis 7 haciendo tres trabajos a la vez: caché, colas y adapter de Socket.IO para que el realtime funcione con varias instancias del API.

La pieza que más ha rendido es packages/api-client: Swagger escribe el openapi.json, openapi-typescript genera los tipos y openapi-fetch los consume. El contrato entre el backend y el panel web lo verifica el compilador, no una revisión manual. Cuando cambia un endpoint, el frontend deja de compilar antes de que nadie despliegue nada.

Qué resuelve

  • Agenda y citas. Disponibilidad real por sede a partir de horarios, bloqueos, reglas de capacidad y excepciones. Estados explícitos —incluido no llegó— con historial de cada cambio.
  • Punto de venta y caja. Precios por sede, caja con apertura, movimientos y cierre. No se puede vender sin caja abierta: eso es lo que permite cuadrar el día.
  • Facturación electrónica SUNAT. Boletas y facturas contra el proveedor de cada razón social, con código anexo y serie propios por sede. Anulación con ticket, log de sincronización y reporte de facturación.
  • Inventario multi-sede. Stock por local, traslados, retiros, compras y proveedores, con valoración.
  • Suscripciones. Planes de sesiones que se consumen automáticamente al vender, y expiran solos vía worker.
  • Reportes. Siete vistas —operaciones, ventas, clientes, no-shows, inventario, planes y resumen— con contexto de periodo y sede, o agregando todas las sedes a la vez.
  • Notificaciones. Push al personal vía Expo, y recordatorios de cita por WhatsApp Cloud API.

Decisiones clave

  • La sede es una dimensión de primer nivel, no un filtro añadido después. Casi todo cuelga de branchId con índices compuestos. Añadirlo más tarde habría obligado a migrar todas las tablas.
  • La serie y el token SUNAT se resuelven por el vínculo sede ↔ razón social, no por configuración global. Es lo que hace posible que dos empresas emitan desde el mismo sistema sin pisarse.
  • La caja como objeto con ciclo de vida en lugar de una suma de ventas. Sin eso no hay forma de detectar un descuadre.
  • Permisos granulares (appointment.read, sale.refund…) con usuarios asignados a sedes, espejados en web y móvil desde el mismo paquete compartido.
  • El no llegó se marca a mano. El job automático existe y está desactivado por decisión de QA: preferimos un falso negativo a marcar como ausente a alguien que sí vino.

Qué queda por delante

Un sistema en producción con usuarios reales no está "terminado", y conviene decirlo:

  • WhatsApp corre en modo simulado. El código está completo —servicio, crons, webhook, parser de respuestas, opt-outs, plantillas y logs— pero espera la verificación del negocio en Meta para enviar de verdad.
  • El portal del paciente se quedó en demo para este release. La UI está hecha; la conexión al API real es la siguiente fase.
  • Sin CI ni monitoreo todavía. Los tests del backend existen y pasan, pero se corren en local y el despliegue es manual. Es la deuda que más pesa y la primera de la lista.

Aprendizajes

Lo que más cambió el sistema no fueron las funciones grandes, sino los detalles que solo aparecen usándolo: que la caja necesitaba egresos además de ingresos, que el stock bajo debía avisar en el panel y no en un reporte, que la agenda se consulta más por día que por semana.

Y una lección que vale para cualquier integración fiscal: la emisión electrónica es un sistema externo que va a fallar. Diseñarla como "la venta no existe hasta que SUNAT responda" habría convertido cada caída del proveedor en una caja parada.

Resultado

Diecisiete sedes operando sobre un mismo sistema: la agenda, el cobro, la facturación electrónica, el inventario y las suscripciones ocurren en el mismo lugar, en web y en móvil, y al cerrar el día las cifras salen del sistema en vez de reconstruirse a mano.

Proyectos relacionados

IntegracionesConcept

CRM + Ecommerce: un solo cliente, no dos

Integración entre HubSpot y la tienda online: pedidos, carritos y valor de cliente dentro del CRM, y automatizaciones que se disparan con datos reales de compra.

El equipo deja de saltar entre dos sistemas

HubSpot APIWooCommerceNode.jsWebhooksPostgreSQL
Ver caso