# Zizu: Una plataforma de delivery para todos los comercios de una ciudad, con cobros online.

> El vecino pide, el comercio prepara, el repartidor lleva y la administración controla todo, cada uno con su pantalla y el mismo pedido actualizándose en vivo.

Rubro: Delivery para varios comercios, Argentina · Estado: En producción · Desarrollado por Ricardo Brossard
URL: https://ricardobrossard.com/proyectos/zizu/

- zizu.com.ar: https://zizu.com.ar

## En números

- **152** pruebas de punta a punta en cada cambio, más 57 unitarias
- **118** migraciones versionadas que reconstruyen toda la base
- **30** tablas con permisos por rol, con 64 políticas
- **4** etapas entregadas, auditadas y facturadas

## El problema

En una ciudad chica, pedir comida a domicilio es llamar o escribir por WhatsApp a cada comercio por separado. El comercio anota a mano, busca quién lleve el pedido y cobra como puede. El cliente no sabe en qué está su pedido y nadie sabe cuánto le corresponde a cada comercio.

El encargo era una sola app para todos los comercios de la ciudad, con repartidores propios y cobro con Mercado Pago o en efectivo.

## Qué construí

Zizu, una plataforma de delivery donde **el vecino pide, el comercio prepara, el repartidor lleva y la administración controla todo**, cada uno desde su pantalla y viendo el mismo pedido actualizarse en vivo. La construí solo y de cero, y la entregué en cuatro etapas, cada una auditada y facturada.

Es una sola aplicación web en **React 18 con Vite y TypeScript**, instalable en el celular, con cuatro áreas según el rol. Cada área se carga por separado y se enciende con un interruptor por etapa: el código de un área apagada ni siquiera viaja al navegador.

El backend entero vive en **Supabase**, con 90 funciones de base, 5 Edge Functions y 3 tareas programadas con pg_cron:

- **Las reglas están en la base, no en las pantallas.** Row Level Security en las 30 tablas, con 64 políticas: un comercio no lee los pedidos de otro, y un repartidor dado de baja pierde en el acto el acceso a los datos de sus clientes.
- **La máquina de estados del pedido corre en el servidor.** Pendiente, aceptado, en preparación, listo, en camino, entregado: cada rol ejecuta sólo sus propias transiciones, aunque alguien llame a la API a mano.
- **Tiempo real.** El pedido nuevo suena en la pantalla del comercio sin recargar, el "listo" aparece en la lista de repartidores y el cliente ve cada paso.
- **El precio lo calcula la base**, con opciones y extras por producto. El total siempre sale del servidor.

También resuelve la operación diaria: tandas de hasta 4 pedidos por repartidor, horarios con excepciones que vencen solas, liquidaciones que cuentan sólo lo cobrado y auditoría de sólo agregar sobre 9 tablas.

## Lo más difícil

**Cobrar una sola vez, y que cada peso se pueda explicar.** El aviso de pago llega a una Edge Function que valida la firma, descarta repetidos (Mercado Pago reintenta cada 15 minutos), consulta el pago directo a la API de Mercado Pago y le pasa el hecho a una función de la base que decide: acreditar, rechazar, registrar un contracargo o una devolución. El aviso transporta; la base decide.

Si el cliente reintenta el pago o abre dos pestañas, recibe el mismo cobro: guardo el intento vivo en la base y, si existe, ni se llama a Mercado Pago. Lo decidí después de probarlo, porque la clave de idempotencia de Mercado Pago no cubre la creación del cobro. Todo termina en un **libro de cobros de sólo agregar**, protegido por triggers: un asiento no se edita ni se borra, ni siquiera con acceso directo a la base, y la comisión queda sellada en cada pedido.

**Que el repartidor llegue a la puerta correcta.** Para convertir una dirección escrita en un punto del mapa armé dos capas con caché: primero OpenStreetMap y, de respaldo, Google. En un sistema así la falla peligrosa es el falso positivo: una consulta con sólo un número devolvía una puerta en otra ciudad a 500 km, y Google la marcaba con su máxima precisión. Por eso **cada resultado se valida** por localidad, distancia al centro, nombre de la calle y número de puerta, y si no pasa, se escala a la capa siguiente. Sin número de puerta no hay alfiler: un punto dudoso es peor que ninguno, porque el repartidor le cree. Las guardas son funciones puras, con pruebas unitarias sobre el callejero real de la ciudad.

## Cómo se prueba

Cada cambio corre en **GitHub Actions**: 57 pruebas unitarias, chequeo de tipos, build y **152 pruebas de punta a punta con Playwright** en un navegador real. Corren contra un ambiente de prueba separado e idéntico a producción, levantado con las mismas 118 migraciones. Cada push a la rama principal se publica solo en **Vercel**.

- **Concurrencia real.** Una prueba hace que dos repartidores tomen el mismo pedido a la vez: tomar es una sola operación atómica en la base, y gana uno solo.
- **Los cobros, caso por caso.** Sub-cobro y sobre-cobro, contracargos, devoluciones que respetan el cobro bueno, reintentos y el checkout abandonado que se cancela solo a los 30 minutos.
- **Datos aislados.** La suite crea sus propios datos de prueba, los borra al terminar y falla si el libro de cobros real creció durante la corrida.

## Stack

- Aplicación: React 18, Vite, TypeScript, Tailwind CSS, TanStack Query, Zustand, Leaflet, PWA con Workbox
- Datos y backend: Supabase, PostgreSQL, Row Level Security, Realtime, Auth, Storage, Edge Functions en Deno, pg_cron
- Integraciones: Mercado Pago, OpenStreetMap, Google Geocoding, Resend
- Calidad y despliegue: Playwright, node:test, GitHub Actions, Vercel
