# Vale AI — Anexo Fase 0: Execution Contract

**Acompaña a:** `vale-master-doc.md` (v5.1) · **Fecha:** 7 jun 2026
**Para:** Rivas + Claude Code · **Naturaleza:** contrato técnico ejecutable. El master doc dice *qué* y *en qué orden*; este anexo dice *cuándo está terminado* y *qué nunca hacer*.

> **Regla global que rige todo este anexo:** ninguna tarea se considera terminada sin (1) criterio de aceptación cumplido, (2) prueba manual hecha y (3) log limpio. Crear la columna no es terminar.

> **Prioridad:** las secciones 1–7 son **bloqueantes del lanzamiento de esta semana**. Las secciones 8 (fast-follow) y 9 (Fase 1) **no bloquean el launch** — están acá para que existan, no para frenar el commit base.

---

## 1. Definition of Done — los 5 pasos de la Fase 0

### Paso 1 — Git Base
- [ ] Repo privado creado; baseline commiteado.
- [ ] `grep` de secretos corrido; **ningún** secreto hardcodeado queda en archivos trackeados.
- [ ] `.env` y `config.php` fuera de Git; `config.example.php` y `.env.example` commiteados con placeholders.
- [ ] `.htaccess` trackeado, con `RedirectMatch 404 /\.git`.
- [ ] `BASE_URL` parametrizada (no hardcodeada) → apunta a `valeai.app` en prod.

### Paso 2 — Soft-Delete + Evidencia
- [ ] **Dump completo de la DB tomado ANTES de migrar.**
- [ ] Migración `026_soft_delete_evidencia.sql` corrida y verificada **en staging (`vale.supraide.com`) primero**, después en prod.
- [ ] `tenants` tiene `deleted_at`, `is_test`, `relationship`, `evidence_consent`.
- [ ] **Ninguna** query que lea tenants activos retorna soft-deleted; se excluye con `deleted_at IS NULL` **respetando el alias de tabla cuando aplique** (`t.deleted_at`, `tenants.deleted_at`). Auditar todas las queries.
- [ ] El reset del dashboard usa `UPDATE` (soft), nunca `DELETE`.
- [ ] Rollback del `026` documentado.

### Paso 3 — Fix `thinkingBudget: 0`
- [ ] Toda llamada multi-turn envía `thinkingBudget: 0`.
- [ ] `buildHistory()` no inventa `thoughtSignature` (solo lo preserva pasivamente si un fallback lo emite).
- [ ] Conversación de **3+ turnos con tool call** sin `400 INVALID_ARGUMENT`.
- [ ] Nombres de modelo por env: `MODEL_PRIMARY` = modelo Gemini activo aprobado para producción en Vertex; `MODEL_FALLBACK` = respaldo estable. Nunca hardcodear el nombre del modelo en código.

### Paso 4 — OAuth Scopes + Calendario Secundario
- [ ] Código pide **solo** los 5 scopes no-sensibles (`openid`, `email`, `profile`, `calendar.app.created`, `freebusy`).
- [ ] Redirect URIs apuntan **exclusivamente** a `valeai.app`.
- [ ] App confirmada "In production".
- [ ] Vale crea/usa el calendario secundario "Vale AI"; toda cita se lee/escribe **solo** ahí.
- [ ] Validado en Google Cloud Console que los scopes figuran como no-sensibles; evidencia guardada en `/docs/compliance/google-oauth-scopes.md`.

### Paso 5 — Resend (DNS) + Política de Privacidad
- [ ] Envío migrado de `mail()`/PHPMailer a la API de Resend (cURL POST).
- [ ] Dominio `valeai.app` verificado en Resend (SPF/DKIM en la zona DNS). Si el DNS aún no propagó/verificó: usar dominio sandbox o **no activar** el flujo de email hasta que verifique — nunca mandar desde dominio sin verificar.
- [ ] Email de prueba llega a **bandeja principal** (no spam).
- [ ] `/privacy` publicada, real (declara uso de email, datos de Google Calendar, WhatsApp y Stripe) y linkeada desde onboarding y la pantalla de OAuth.

---

## 2. Idempotencia obligatoria (anti-duplicados)

Webhooks de Meta y Stripe **se repiten** ante timeouts o reintentos. Sin esto: citas dobles, pagos dobles, respuestas dobles.

Llaves `UNIQUE` en la DB:
- `meta_message_id UNIQUE`
- `stripe_event_id UNIQUE`
- `resend_email_id UNIQUE`

Regla general: si el ID ya existe → no reprocesar, devolver `200 OK` pasivo y salir.

**Calendar (caso especial):** toda cita tiene un `appointment_id` interno. **Antes** de crear el evento en Google, se consulta si ese `appointment_id` ya tiene `google_calendar_event_id`; si lo tiene, no se crea otro. Esto evita la cita doble cuando la respuesta de Google se pierde después de haber creado el evento.

---

## 3. Política de degradación (lo crítico, no la matriz completa)

| Falla | Comportamiento obligatorio |
| --- | --- |
| **Google Calendar** | **NO confirmar la cita.** Tomar los datos del cliente y avisar que se confirmará. Nunca decir "agendado" si el evento no entró. |
| **Vertex AI / Gemini** | Responder con fallback humano seguro + log crítico (con `request_id`, `tenant_id`, `conversation_id`, modelo, payload sanitizado). |
| **Resend** | Guardar el email pendiente en `outbox` para reintento. |
| **Stripe webhook duplicado** | Idempotencia por `stripe_event_id`. |
| **Meta webhook repetido** | Idempotencia por `meta_message_id`. |

---

## 4. Logging seguro — sanitización de `agent_logs`

`agent_logs` **NUNCA** debe guardar: access tokens, refresh tokens, passwords, API keys, headers `Authorization`, payload completo de OAuth, datos de pago/tarjetas, ni **datos clínicos**.

Loguear solo lo necesario para diagnóstico (ids, modelo, conteo de tokens, función llamada, payload sanitizado).

---

## 5. Minimización de PHI (vertical médico, Etapa 1)

No se afirma "Riesgo PHI: cero". Se afirma **PHI minimizado**:
- Vale no solicita ni almacena información clínica en columnas estructuradas en Etapa 1.
- Si el paciente comparte datos clínicos **espontáneamente** (ej: "me duele la muela", "seguimiento de mi cirugía"): Vale **no diagnostica, no interpreta y no persiste** esos detalles como datos clínicos, **no los manda al modelo más allá de lo necesario para agendar**, y **redirige a una acción administrativa** (agendar, cancelar, reprogramar) o **deriva al personal humano**.

---

## 6. No tocar / No hacer (para Claude Code y Rivas)

**Prohibido:**
- Migrar a framework / agregar colas pesadas (Redis, RabbitMQ), Docker, microservicios.
- `HARD DELETE` sobre `tenants` (siempre soft-delete).
- Hardcodear dominios o nombres de modelos.
- Commitear dumps `.sql` o secretos.
- Agregar scopes amplios de Calendar sin aprobación explícita.
- Guardar PHI en prompts o logs.
- Enviar campañas/recordatorios de WhatsApp sin plantilla aprobada + opt-in.
- Confirmar citas si Calendar falló.
- **Modificar `buildHistory()` sin correr antes la prueba de conversación de 3 turnos con tool call.**

---

## 7. Pruebas manuales mínimas (antes de declarar Fase 0 lista)

1. Onboarding de barbería desde cero.
2. Segundo (y tercer) mensaje sin `INVALID_ARGUMENT`.
3. Crear cita real en el calendario secundario "Vale AI".
4. Tenant soft-deleted no aparece en el dashboard.
5. Reset preserva `agent_logs` / evidencia.
6. Webhook de WhatsApp responde `200` en <5s.
7. Tenant Free bloqueado tras 30 mensajes/día (+ throttle por IP).
8. Feedback negativo **no** bloquea el link de reseña de Google (va a todos por igual).

---

## 8. FAST-FOLLOW — no bloquea el launch (al prender Growth/Pro)

- **Outbox completo (`outbox_jobs`):** tabla simple (`id, tenant_id, type, payload_json, status, attempts, next_attempt_at, last_error`) para recordatorios, campañas, alertas al staff y reintentos. Para el launch alcanza un retry mínimo del mail de Resend (sección 3).
- **Límites granulares por tenant:** máx conversaciones/día, máx tokens/conversación, máx citas/hora, máx templates WhatsApp/día, cooldown por contacto. (El cap de 30/día + IP ya cubre el techo de costo del launch.)
- **Matriz de degradación completa** más allá de las fallas críticas de la sección 3.

---

## 9. FASE 1 — refactor post-lanzamiento (spec en papel, implementación después)

Los contratos del Orchestrator se definen acá ahora (barato, le dan blanco al refactor); se **implementan** en Fase 1, no esta semana.

**AgentInput:**
```json
{
  "tenant_id": "string",
  "channel": "web|whatsapp|dashboard",
  "conversation_id": "string",
  "contact_id": "string|null",
  "message_text": "string",
  "message_type": "text|interactive|media",
  "locale": "es-NI|es-US|en-US",
  "metadata": {}
}
```

**AgentOutput:**
```json
{
  "reply_text": "string",
  "actions": [],
  "scheduled_events": [],
  "handoff_required": false,
  "log_level": "normal|warning|error",
  "usage": { "model": "string", "input_tokens": 0, "output_tokens": 0 }
}
```

---

## 10. Rollback (antes de cada migración)

- Exportar backup MySQL y guardar nombre/hash del archivo.
- Confirmar script `down` o procedimiento de rollback manual.
- Correr primero en staging (`vale.supraide.com`).
- No desplegar migración un viernes a la noche sin acceso a cPanel.
