@vitrina/api 13.5.0 → 13.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.cts +2364 -473
  2. package/dist/index.d.ts +2364 -473
  3. package/package.json +1 -1
package/dist/index.d.cts CHANGED
@@ -7451,7 +7451,7 @@ interface paths {
7451
7451
  };
7452
7452
  /**
7453
7453
  * Get the funnel report
7454
- * @description The board's Pasos del embudo (ADR 0132) with the number of leads that reached each one in the period, the conversion from the step before, the split by stage inside each step, and Perdido apart. Counts only: this is the door the aggregates-only Consultor seat reads, next to `boards` lists the sales boards that can be read (id + name) so a board picker needs no other scope; `pipeline_id` selects one. `leads:read`. WHAT A COUNT MEANS. The cohort is the leads of the board CREATED in the period (`from` to `to`, both days inclusive, in the workspace clock). A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the creation date but how far it got is read as of now: a lead created in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.
7454
+ * @description The board's Pasos del embudo (ADR 0132) with the number of leads that reached each one in the period, the conversion from the step before, the split by stage inside each step, and Perdido apart. Counts only: this is the door the aggregates-only Consultor seat reads, next to `boards` lists the sales boards that can be read (id + name) so a board picker needs no other scope; `pipeline_id` selects one. The last step (Venta) carries `money`: what its people paid since their lead was created (`null` = more than 2 000 people, or unknown). `leads:read`. WHAT A COUNT MEANS. The cohort is the leads of the board CREATED in the period (`from` to `to`, both days inclusive, in the workspace clock). A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the creation date but how far it got is read as of now: a lead created in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.
7455
7455
  */
7456
7456
  get: {
7457
7457
  parameters: {
@@ -7517,7 +7517,8 @@ interface paths {
7517
7517
  * "current": 14,
7518
7518
  * "conversion_from_previous": 0.4,
7519
7519
  * "conversion_from_start": 0.4,
7520
- * "stages": []
7520
+ * "stages": [],
7521
+ * "money": 3450000
7521
7522
  * }
7522
7523
  * ],
7523
7524
  * "lost": {
@@ -7813,7 +7814,7 @@ interface paths {
7813
7814
  };
7814
7815
  /**
7815
7816
  * List the people behind a funnel count
7816
- * @description The leads behind one number of `GET /funnel-report`, read through the same cohort and the same rules, so the list always has as many rows as the count says (`total`). `measure=reached&step=` is a step headline; `measure=current` is who stands there today; `measure=lost` is Perdido (narrow with `step`, or `lost_from_unknown=true`); `measure=unplaced` is the leads on a stage that belongs to no step. Add `stage_id` for one stage of a step. Newest activity first. Needs `leads:read`; the contact name rides only with `contacts:read`. WHAT A COUNT MEANS. The cohort is the leads of the board CREATED in the period (`from` to `to`, both days inclusive, in the workspace clock). A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the creation date but how far it got is read as of now: a lead created in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.
7817
+ * @description The leads behind one number of `GET /funnel-report`, read through the same cohort and the same rules, so the list always has as many rows as the count says (`total`). `measure=reached&step=` is a step headline; `measure=current` is who stands there today; `measure=lost` is Perdido (narrow with `step`, or `lost_from_unknown=true`); `measure=unplaced` is the leads on a stage that belongs to no step. Add `stage_id` for one stage of a step. Newest activity first. Needs `leads:read`; the contact name and phone ride only with `contacts:read`. D60: each person also carries `score` and `score_reason` («qué pasó»), `lost_reason` («motivo»), the next cita (else the last, `cita_at`/`cita_status`), `paid` (CLP the clinic recorded since the lead was created) and, for a clinic, `origin` (Meta naming the ad, Orgánico, Sin dato). WHAT A COUNT MEANS. The cohort is the leads of the board CREATED in the period (`from` to `to`, both days inclusive, in the workspace clock). A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the creation date but how far it got is read as of now: a lead created in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.
7817
7818
  */
7818
7819
  get: {
7819
7820
  parameters: {
@@ -7878,7 +7879,24 @@ interface paths {
7878
7879
  * "last_activity_at": "2026-10-08T09:30:00.000Z",
7879
7880
  * "last_move_at": "2026-09-20T16:00:00.000Z",
7880
7881
  * "last_move_stage_name": "Evaluación agendada",
7881
- * "reached_step_key": "scheduled"
7882
+ * "reached_step_key": "scheduled",
7883
+ * "phone": "+56 9 1234 5678",
7884
+ * "score": 72,
7885
+ * "score_reason": "Pidió hora para evaluación de implante; preguntó por cuotas.",
7886
+ * "lost_reason": null,
7887
+ * "cita_at": "2026-10-14T13:00:00.000Z",
7888
+ * "cita_status": "confirmed",
7889
+ * "paid": 45000,
7890
+ * "origin": {
7891
+ * "kind": "meta",
7892
+ * "label": "Meta · Implantes 2",
7893
+ * "ad_id": "120210000000000101",
7894
+ * "ad_name": "Implantes 2",
7895
+ * "ad_set_name": "INTERESES",
7896
+ * "campaign_name": "IMPLANTES",
7897
+ * "first_touch_at": "2026-09-12T14:01:00.000Z",
7898
+ * "via": "first_message"
7899
+ * }
7882
7900
  * }
7883
7901
  * ]
7884
7902
  * }
@@ -12690,7 +12708,7 @@ interface paths {
12690
12708
  "application/json": {
12691
12709
  /** Format: uri */
12692
12710
  url?: string;
12693
- events?: ("*" | "ai_agent.publish" | "ai_agent_graph.publish" | "agent_scenario_run.completed" | "agent_scenario_suite.completed" | "conversation.created" | "conversation.assigned" | "conversation.resolved" | "message.received" | "message.sent" | "ticket.opened" | "ticket.reopened" | "ticket.resolved" | "ticket.closed" | "sla.breached" | "kb.source.create" | "kb.source.embed" | "kb.source.update" | "kb.source.delete" | "kb_file.uploaded" | "kb_file.deleted" | "skill.created" | "skill.updated" | "skill.deleted" | "custom_tool.created" | "custom_tool.updated" | "custom_tool.deleted" | "macro.applied" | "cost.budget_alert" | "runtime.budget_exceeded" | "lead.created" | "lead.stage_changed" | "lead.pipeline_changed" | "lead.assigned" | "lead.won" | "lead.lost" | "lead.unqualified" | "stock.synced" | "stock.source_retired" | "vehicle.published" | "vehicle.unpublished" | "vehicle.publish_failed" | "vehicle.created" | "vehicle.attachment.created" | "mercadolibre.order" | "mercadolibre.vis_lead" | "chileautos.lead" | "yapo.lead" | "website.lead" | "demo_lead.created" | "demo_lead.pending_scheduling" | "demo_lead.routed" | "demo_booking.booked" | "demo_booking.rescheduled" | "demo_booking.cancelled" | "consignment.candidate.created" | "quote.issued" | "quote.voided" | "quote.extended" | "quote.archived" | "reservation.created" | "reservation.voided" | "reservation.abono_disposed" | "reservation.converted" | "sale_note.issued" | "sale_note.approved" | "sale_note.voided" | "purchase_note.issued" | "purchase_note.corrected" | "purchase_note.voided" | "payment.recorded" | "consignment.created" | "consignment.returned" | "consignment.sold" | "credit_application.recorded" | "credit_application.submitted" | "credit_application.decided" | "credit_application.withdrawn" | "price_approval.requested" | "price_approval.decided" | "contact.created" | "contact.updated" | "contact.merged" | "appointment.booked" | "appointment.rescheduled" | "appointment.cancelled" | "appointment.completed" | "appointment.no_show" | "appointment.reminded" | "appointment.imported" | "campaign.sent" | "campaign.paused" | "sending_domain.verified" | "coach.proposals_ready" | "coach.finding_ready" | "voice.autonomous_call" | "safety_hold.placed" | "safety_hold.ack_overdue" | "safety_hold.first_response_overdue" | "team.created" | "team.updated" | "team.deleted" | "custom_role.created" | "custom_role.updated" | "custom_role.deleted" | "macro.created" | "macro.updated" | "macro.deleted" | "assignment_rule.created" | "assignment_rule.updated" | "assignment_rule.deleted" | "sla_policy.created" | "sla_policy.updated" | "sla_policy.deleted" | "automation_trigger.created" | "automation_trigger.updated" | "automation_trigger.deleted" | "sending_domain.created" | "sending_domain.deleted" | "whatsapp_template.created" | "whatsapp_template.status_changed" | "whatsapp_template.deleted" | "whatsapp_flow.created" | "whatsapp_flow.published" | "whatsapp_flow.deprecated" | "whatsapp_flow.deleted" | "whatsapp_flow.completed" | "channel.connected" | "channel.updated" | "channel.disconnected" | "membership.created" | "membership.updated" | "membership.removed" | "clinic_professional.created" | "clinic_professional.updated" | "clinic_professional.deleted" | "clinic_service.created" | "clinic_service.updated" | "clinic_service.deleted" | "clinic_price_list.updated" | "clinic_pack_purchase.created" | "clinic_pack_purchase.session_consumed" | "clinic_pack_purchase.cancelled" | "clinic_budget.created" | "clinic_budget.sent" | "clinic_budget.accepted" | "clinic_budget.rejected" | "clinic_budget.voided" | "clinic_budget.mirror_changed" | "clinic_cash_session.opened" | "clinic_cash_session.closed" | "clinic_patient.created" | "clinic_patient.updated" | "clinic_patient.deleted" | "clinic_patient.merged" | "clinic_encounter.opened" | "clinic_encounter.closed" | "clinic_note.signed" | "clinic_note.amended" | "clinic_consent.signed" | "clinic_consent.revoked" | "clinic_document.uploaded" | "clinic_document.deleted" | "clinic_privacy_request.completed")[];
12711
+ events?: ("*" | "ai_agent.publish" | "ai_agent_graph.publish" | "agent_scenario_run.completed" | "agent_scenario_suite.completed" | "conversation.created" | "conversation.assigned" | "conversation.resolved" | "message.received" | "message.sent" | "ticket.opened" | "ticket.reopened" | "ticket.resolved" | "ticket.closed" | "sla.breached" | "kb.source.create" | "kb.source.embed" | "kb.source.update" | "kb.source.delete" | "kb_file.uploaded" | "kb_file.deleted" | "skill.created" | "skill.updated" | "skill.deleted" | "custom_tool.created" | "custom_tool.updated" | "custom_tool.deleted" | "macro.applied" | "cost.budget_alert" | "runtime.budget_exceeded" | "lead.created" | "lead.stage_changed" | "lead.pipeline_changed" | "lead.assigned" | "lead.won" | "lead.lost" | "lead.unqualified" | "stock.synced" | "stock.source_retired" | "vehicle.published" | "vehicle.unpublished" | "vehicle.publish_failed" | "vehicle.created" | "vehicle.attachment.created" | "mercadolibre.order" | "mercadolibre.vis_lead" | "chileautos.lead" | "yapo.lead" | "website.lead" | "demo_lead.created" | "demo_lead.pending_scheduling" | "demo_lead.routed" | "demo_booking.booked" | "demo_booking.rescheduled" | "demo_booking.cancelled" | "demo_booking.held" | "demo_booking.no_show" | "consignment.candidate.created" | "quote.issued" | "quote.voided" | "quote.extended" | "quote.archived" | "reservation.created" | "reservation.voided" | "reservation.abono_disposed" | "reservation.converted" | "sale_note.issued" | "sale_note.approved" | "sale_note.voided" | "purchase_note.issued" | "purchase_note.corrected" | "purchase_note.voided" | "payment.recorded" | "consignment.created" | "consignment.returned" | "consignment.sold" | "credit_application.recorded" | "credit_application.submitted" | "credit_application.decided" | "credit_application.withdrawn" | "price_approval.requested" | "price_approval.decided" | "contact.created" | "contact.updated" | "contact.merged" | "appointment.booked" | "appointment.rescheduled" | "appointment.cancelled" | "appointment.completed" | "appointment.no_show" | "appointment.reminded" | "appointment.imported" | "campaign.sent" | "campaign.paused" | "sending_domain.verified" | "coach.proposals_ready" | "coach.finding_ready" | "voice.autonomous_call" | "safety_hold.placed" | "safety_hold.ack_overdue" | "safety_hold.first_response_overdue" | "team.created" | "team.updated" | "team.deleted" | "custom_role.created" | "custom_role.updated" | "custom_role.deleted" | "macro.created" | "macro.updated" | "macro.deleted" | "assignment_rule.created" | "assignment_rule.updated" | "assignment_rule.deleted" | "sla_policy.created" | "sla_policy.updated" | "sla_policy.deleted" | "automation_trigger.created" | "automation_trigger.updated" | "automation_trigger.deleted" | "sending_domain.created" | "sending_domain.deleted" | "whatsapp_template.created" | "whatsapp_template.status_changed" | "whatsapp_template.deleted" | "whatsapp_flow.created" | "whatsapp_flow.published" | "whatsapp_flow.deprecated" | "whatsapp_flow.deleted" | "whatsapp_flow.completed" | "channel.connected" | "channel.updated" | "channel.disconnected" | "membership.created" | "membership.updated" | "membership.removed" | "clinic_professional.created" | "clinic_professional.updated" | "clinic_professional.deleted" | "clinic_service.created" | "clinic_service.updated" | "clinic_service.deleted" | "clinic_price_list.updated" | "clinic_pack_purchase.created" | "clinic_pack_purchase.session_consumed" | "clinic_pack_purchase.cancelled" | "clinic_budget.created" | "clinic_budget.sent" | "clinic_budget.accepted" | "clinic_budget.rejected" | "clinic_budget.voided" | "clinic_budget.mirror_changed" | "clinic_cash_session.opened" | "clinic_cash_session.closed" | "clinic_patient.created" | "clinic_patient.updated" | "clinic_patient.deleted" | "clinic_patient.merged" | "clinic_encounter.opened" | "clinic_encounter.closed" | "clinic_note.signed" | "clinic_note.amended" | "clinic_consent.signed" | "clinic_consent.revoked" | "clinic_document.uploaded" | "clinic_document.deleted" | "clinic_privacy_request.completed")[];
12694
12712
  enabled?: boolean;
12695
12713
  description?: string | null;
12696
12714
  /** @description «Incluir datos del recurso». `false` (the default) = every delivery is the notice: resource, changes, author, time and `url`, with `data_omitted: "not_requested"`. `true` = each delivery also carries `data` when, at dispatch time, this subscription's OWNER — whoever created it or last changed its url, events or this flag — holds the resource read scope (`read_scope` in the catalogue) and sees every record; otherwise `data_omitted` says why. An event about a dato sensible is always the notice. */
@@ -82748,7 +82766,7 @@ interface paths {
82748
82766
  patch?: never;
82749
82767
  trace?: never;
82750
82768
  };
82751
- "/appointment-types": {
82769
+ "/appointments/reminders/plan": {
82752
82770
  parameters: {
82753
82771
  query?: never;
82754
82772
  header?: never;
@@ -82756,27 +82774,19 @@ interface paths {
82756
82774
  cookie?: never;
82757
82775
  };
82758
82776
  /**
82759
- * List appointment types
82760
- * @description The per-tenant bookable catalogue, which is ALSO the «Servicios» catalogue: a servicio is an appointment type with a price (there is no `/services` resource). `kind` and `active_only` are the screen’s tabs and `q` is its search box — free text over name AND description, case-insensitive, with `%` and `_` escaped so "50% off" is a literal rather than a wildcard. All three COMPOSE: `?kind=service&active_only=true&q=limpieza` is "the active services whose name or description mentions limpieza". An empty or omitted `q` is simply no text filter.
82761
- *
82762
- * ELIMINATED types (`deleted_at` non-null, see `DELETE /appointment-types/{id}`) are excluded from EVERY combination of these filters — including `active_only=false`, so they never come back as "inactivos". Every row returned here therefore carries `deleted_at: null`; the field is on the wire as an audit fact, not as a state to branch on.
82763
- *
82764
- * `price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.
82777
+ * Get the appointment reminder ladder
82778
+ * @description The rungs, the single-sender declaration, whether anything is being sent right now (`live`) and `blocked_reason` when it is not. A rung has an `event` (`before_start` by default, `booked` for the welcome right after booking, `no_show` for the rebook with three fresh slots), an `email` mode (`never` / `fallback` / `also`, the twin with a .ics) and optional per-kind WhatsApp `templates` (`demo`, `clinic`, `visit`, `generic`). Off for every tenant until its owner saves a plan and declares Vitrina the single sender.
82765
82779
  */
82766
82780
  get: {
82767
82781
  parameters: {
82768
- query?: {
82769
- kind?: ("test_drive" | "external" | "service")[];
82770
- active_only?: boolean | null;
82771
- q?: string;
82772
- };
82782
+ query?: never;
82773
82783
  header?: never;
82774
82784
  path?: never;
82775
82785
  cookie?: never;
82776
82786
  };
82777
82787
  requestBody?: never;
82778
82788
  responses: {
82779
- /** @description Appointment types */
82789
+ /** @description Reminder plan */
82780
82790
  200: {
82781
82791
  headers: {
82782
82792
  [name: string]: unknown;
@@ -82784,52 +82794,62 @@ interface paths {
82784
82794
  content: {
82785
82795
  /**
82786
82796
  * @example {
82787
- * "data": [
82788
- * {
82789
- * "id": "eeee0000-0000-4000-8000-000000000001",
82790
- * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
82791
- * "name": "Consulta de evaluación",
82792
- * "description": "Primera visita: revisión y presupuesto.",
82793
- * "kind": "service",
82794
- * "engine": "native",
82795
- * "external_ref": null,
82796
- * "buffer_minutes": null,
82797
- * "duration_minutes": 45,
82798
- * "price_amount": 35000,
82799
- * "price_clp": 35000,
82800
- * "price_currency": "CLP",
82801
- * "price_is_from": true,
82802
- * "eligible_staff_ids": [],
82803
- * "is_active": true,
82804
- * "is_default": false,
82805
- * "deleted_at": null,
82806
- * "created_at": "2026-09-22T12:50:27.458Z",
82807
- * "updated_at": "2026-09-22T12:50:27.458Z"
82808
- * },
82809
- * {
82810
- * "id": "eeee0000-0000-4000-8000-000000000002",
82811
- * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
82812
- * "name": "Test drive",
82813
- * "description": "Prueba de manejo de 30 minutos, con ejecutivo.",
82814
- * "kind": "test_drive",
82815
- * "engine": "native",
82816
- * "external_ref": null,
82817
- * "buffer_minutes": null,
82818
- * "duration_minutes": 30,
82819
- * "price_amount": null,
82820
- * "price_clp": null,
82821
- * "price_currency": "CLP",
82822
- * "price_is_from": false,
82823
- * "eligible_staff_ids": [],
82824
- * "is_active": true,
82825
- * "is_default": true,
82826
- * "deleted_at": null,
82827
- * "created_at": "2026-06-02T11:02:10.004Z",
82828
- * "updated_at": "2026-06-02T11:02:10.004Z"
82829
- * }
82830
- * ],
82831
- * "meta": {
82832
- * "total": 2
82797
+ * "data": {
82798
+ * "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
82799
+ * "name": "Recordatorios de cita",
82800
+ * "rungs": [
82801
+ * {
82802
+ * "key": "welcome",
82803
+ * "event": "booked",
82804
+ * "offset_hours": 0,
82805
+ * "channel": "whatsapp",
82806
+ * "email": "also",
82807
+ * "template_name": null,
82808
+ * "templates": {
82809
+ * "demo": "demo_confirmada_v1"
82810
+ * },
82811
+ * "asks_confirmation": false,
82812
+ * "include_reschedule_link": true
82813
+ * },
82814
+ * {
82815
+ * "key": "h24",
82816
+ * "offset_hours": 24,
82817
+ * "channel": "whatsapp",
82818
+ * "email": "fallback",
82819
+ * "template_name": "recordatorio_24h",
82820
+ * "asks_confirmation": true,
82821
+ * "include_reschedule_link": true
82822
+ * },
82823
+ * {
82824
+ * "key": "h2",
82825
+ * "offset_hours": 2,
82826
+ * "channel": "whatsapp",
82827
+ * "template_name": "recordatorio_2h",
82828
+ * "asks_confirmation": false,
82829
+ * "include_reschedule_link": false
82830
+ * },
82831
+ * {
82832
+ * "key": "no_show",
82833
+ * "event": "no_show",
82834
+ * "offset_hours": 0.17,
82835
+ * "channel": "whatsapp",
82836
+ * "email": "fallback",
82837
+ * "template_name": "reagendar",
82838
+ * "asks_confirmation": false,
82839
+ * "include_reschedule_link": true
82840
+ * }
82841
+ * ],
82842
+ * "active": true,
82843
+ * "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
82844
+ * "single_sender_confirmed_by": null,
82845
+ * "vendor_sends_reminders": false,
82846
+ * "live": true,
82847
+ * "blocked_reason": null,
82848
+ * "defaults": [],
82849
+ * "updated_at": "2026-10-11T12:00:00.000Z",
82850
+ * "confirm_status_id": null,
82851
+ * "confirm_status": null,
82852
+ * "create_contacts_from_patient_phone": false
82833
82853
  * }
82834
82854
  * }
82835
82855
  */
@@ -82897,26 +82917,14 @@ interface paths {
82897
82917
  };
82898
82918
  };
82899
82919
  };
82900
- put?: never;
82901
82920
  /**
82902
- * Create an appointment type
82903
- * @description Adds a bookable type to the catalogue — a servicio once it carries a price. `duration_minutes` is what sets the slot LENGTH when this type is booked, while the agenda grid stays `slot_minutes` (`GET /appointments/config`).
82904
- *
82905
- * `eligible_staff_ids` narrows who may take it: booking assigns round-robin from that pool, and an empty list means the workspace’s whole sales/professional team. `kind` defaults to `external`.
82906
- *
82907
- * The NAME is unique among the live rows, so re-creating a type you eliminated is allowed and yields a NEW id.
82908
- *
82909
- * `price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.
82910
- *
82911
- * **Depósito / abono.** The deposit rule for this service is NOT on this resource: it is a `deposit_policy` row scoped to `appointment_type`, read and written through `GET|PUT /deposit-policies`. Each booking still takes a SNAPSHOT — the resolved amount and deadline land on the cita’s own `customer_obligation` — so editing the policy never changes what an already-booked customer was told to transfer.
82921
+ * Save the appointment reminder ladder
82922
+ * @description Replaces the rungs. 400 `single_sender_not_confirmed` when `active` is requested without the declaration. Outside the 24 h WhatsApp window a rung needs an APPROVED template on the number (named variables: `nombre`, `fecha`, `hora`, `anfitrion`, `reunion`, `enlace`, `opcion_1`..`opcion_3`); with `email: "fallback"` a missing template falls back to email instead of staying silent.
82912
82923
  */
82913
- post: {
82924
+ put: {
82914
82925
  parameters: {
82915
82926
  query?: never;
82916
- header?: {
82917
- /** @description Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation. */
82918
- "Idempotency-Key"?: string;
82919
- };
82927
+ header?: never;
82920
82928
  path?: never;
82921
82929
  cookie?: never;
82922
82930
  };
@@ -82924,149 +82932,88 @@ interface paths {
82924
82932
  content: {
82925
82933
  /**
82926
82934
  * @example {
82927
- * "name": "Consulta de evaluación",
82928
- * "description": "Primera visita: revisión y presupuesto.",
82929
- * "kind": "service",
82930
- * "duration_minutes": 45,
82931
- * "price_amount": 35000,
82932
- * "price_currency": "CLP",
82933
- * "price_is_from": true
82935
+ * "rungs": [
82936
+ * {
82937
+ * "key": "welcome",
82938
+ * "event": "booked",
82939
+ * "offset_hours": 0,
82940
+ * "channel": "whatsapp",
82941
+ * "email": "also",
82942
+ * "template_name": null,
82943
+ * "templates": {
82944
+ * "demo": "demo_confirmada_v1"
82945
+ * },
82946
+ * "asks_confirmation": false,
82947
+ * "include_reschedule_link": true
82948
+ * },
82949
+ * {
82950
+ * "key": "h24",
82951
+ * "offset_hours": 24,
82952
+ * "channel": "whatsapp",
82953
+ * "email": "fallback",
82954
+ * "template_name": "recordatorio_24h",
82955
+ * "asks_confirmation": true,
82956
+ * "include_reschedule_link": true
82957
+ * },
82958
+ * {
82959
+ * "key": "h2",
82960
+ * "offset_hours": 2,
82961
+ * "channel": "whatsapp",
82962
+ * "template_name": "recordatorio_2h",
82963
+ * "asks_confirmation": false,
82964
+ * "include_reschedule_link": false
82965
+ * },
82966
+ * {
82967
+ * "key": "no_show",
82968
+ * "event": "no_show",
82969
+ * "offset_hours": 0.17,
82970
+ * "channel": "whatsapp",
82971
+ * "email": "fallback",
82972
+ * "template_name": "reagendar",
82973
+ * "asks_confirmation": false,
82974
+ * "include_reschedule_link": true
82975
+ * }
82976
+ * ],
82977
+ * "active": true
82934
82978
  * }
82935
82979
  */
82936
82980
  "application/json": {
82937
- name: string;
82938
- description?: string | null;
82939
- /** @enum {string} */
82940
- kind?: "test_drive" | "external" | "service";
82941
- duration_minutes: number;
82942
- price_amount?: number | null;
82943
- price_currency?: string;
82944
- price_is_from?: boolean;
82945
- eligible_staff_ids?: string[];
82946
- is_active?: boolean;
82947
- };
82948
- };
82949
- };
82950
- responses: {
82951
- /** @description Appointment type */
82952
- 201: {
82953
- headers: {
82954
- [name: string]: unknown;
82955
- };
82956
- content: {
82957
- /**
82958
- * @example {
82959
- * "id": "eeee0000-0000-4000-8000-000000000001",
82960
- * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
82961
- * "name": "Consulta de evaluación",
82962
- * "description": "Primera visita: revisión y presupuesto.",
82963
- * "kind": "service",
82964
- * "engine": "native",
82965
- * "external_ref": null,
82966
- * "buffer_minutes": null,
82967
- * "duration_minutes": 45,
82968
- * "price_amount": 35000,
82969
- * "price_clp": 35000,
82970
- * "price_currency": "CLP",
82971
- * "price_is_from": true,
82972
- * "eligible_staff_ids": [],
82973
- * "is_active": true,
82974
- * "is_default": false,
82975
- * "deleted_at": null,
82976
- * "created_at": "2026-09-22T12:50:27.458Z",
82977
- * "updated_at": "2026-09-22T12:50:27.458Z"
82978
- * }
82979
- */
82980
- "application/json": {
82981
- data?: unknown;
82982
- };
82983
- };
82984
- };
82985
- /** @description Validation error */
82986
- 400: {
82987
- headers: {
82988
- [name: string]: unknown;
82989
- };
82990
- content: {
82991
- "application/json": components["schemas"]["Error"];
82992
- };
82993
- };
82994
- /** @description Unauthorized */
82995
- 401: {
82996
- headers: {
82997
- [name: string]: unknown;
82998
- };
82999
- content: {
83000
- "application/json": components["schemas"]["Error"];
83001
- };
83002
- };
83003
- /** @description Forbidden */
83004
- 403: {
83005
- headers: {
83006
- [name: string]: unknown;
83007
- };
83008
- content: {
83009
- "application/json": components["schemas"]["Error"];
83010
- };
83011
- };
83012
- /** @description Not found */
83013
- 404: {
83014
- headers: {
83015
- [name: string]: unknown;
83016
- };
83017
- content: {
83018
- "application/json": components["schemas"]["Error"];
83019
- };
83020
- };
83021
- /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83022
- 409: {
83023
- headers: {
83024
- [name: string]: unknown;
83025
- };
83026
- content: {
83027
- "application/json": components["schemas"]["Error"];
83028
- };
83029
- };
83030
- /** @description Rate limited */
83031
- 429: {
83032
- headers: {
83033
- [name: string]: unknown;
83034
- };
83035
- content: {
83036
- "application/json": components["schemas"]["Error"];
82981
+ name?: string;
82982
+ rungs: {
82983
+ key: string;
82984
+ offset_hours: number;
82985
+ /** @enum {string} */
82986
+ event?: "before_start" | "booked" | "no_show";
82987
+ /** @enum {string} */
82988
+ email?: "never" | "fallback" | "also";
82989
+ templates?: {
82990
+ demo?: string;
82991
+ clinic?: string;
82992
+ visit?: string;
82993
+ generic?: string;
82994
+ };
82995
+ /**
82996
+ * @default whatsapp
82997
+ * @enum {string}
82998
+ */
82999
+ channel?: "whatsapp" | "email";
83000
+ template_name?: string | null;
83001
+ /** @default false */
83002
+ asks_confirmation?: boolean;
83003
+ /** @default true */
83004
+ include_reschedule_link?: boolean;
83005
+ }[];
83006
+ active: boolean;
83007
+ vendor_sends_reminders?: boolean;
83008
+ /** Format: uuid */
83009
+ confirm_status_id?: string | null;
83010
+ create_contacts_from_patient_phone?: boolean;
83011
+ quiet_hours_enforced?: boolean;
83037
83012
  };
83038
83013
  };
83039
83014
  };
83040
- };
83041
- delete?: never;
83042
- options?: never;
83043
- head?: never;
83044
- patch?: never;
83045
- trace?: never;
83046
- };
83047
- "/appointment-types/{id}": {
83048
- parameters: {
83049
- query?: never;
83050
- header?: never;
83051
- path?: never;
83052
- cookie?: never;
83053
- };
83054
- /**
83055
- * Get an appointment type
83056
- * @description By uuid. An ELIMINATED type is a 404 here even though the appointments booked against it still resolve its name — the catalogue and the history answer different questions.
83057
- */
83058
- get: {
83059
- parameters: {
83060
- query?: never;
83061
- header?: never;
83062
- path: {
83063
- id: string;
83064
- };
83065
- cookie?: never;
83066
- };
83067
- requestBody?: never;
83068
83015
  responses: {
83069
- /** @description Appointment type */
83016
+ /** @description Reminder plan */
83070
83017
  200: {
83071
83018
  headers: {
83072
83019
  [name: string]: unknown;
@@ -83074,25 +83021,935 @@ interface paths {
83074
83021
  content: {
83075
83022
  /**
83076
83023
  * @example {
83077
- * "id": "eeee0000-0000-4000-8000-000000000001",
83078
- * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
83079
- * "name": "Consulta de evaluación",
83080
- * "description": "Primera visita: revisión y presupuesto.",
83081
- * "kind": "service",
83082
- * "engine": "native",
83083
- * "external_ref": null,
83084
- * "buffer_minutes": null,
83085
- * "duration_minutes": 45,
83086
- * "price_amount": 35000,
83087
- * "price_clp": 35000,
83088
- * "price_currency": "CLP",
83089
- * "price_is_from": true,
83090
- * "eligible_staff_ids": [],
83091
- * "is_active": true,
83092
- * "is_default": false,
83093
- * "deleted_at": null,
83094
- * "created_at": "2026-09-22T12:50:27.458Z",
83095
- * "updated_at": "2026-09-22T12:50:27.458Z"
83024
+ * "data": {
83025
+ * "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
83026
+ * "name": "Recordatorios de cita",
83027
+ * "rungs": [
83028
+ * {
83029
+ * "key": "welcome",
83030
+ * "event": "booked",
83031
+ * "offset_hours": 0,
83032
+ * "channel": "whatsapp",
83033
+ * "email": "also",
83034
+ * "template_name": null,
83035
+ * "templates": {
83036
+ * "demo": "demo_confirmada_v1"
83037
+ * },
83038
+ * "asks_confirmation": false,
83039
+ * "include_reschedule_link": true
83040
+ * },
83041
+ * {
83042
+ * "key": "h24",
83043
+ * "offset_hours": 24,
83044
+ * "channel": "whatsapp",
83045
+ * "email": "fallback",
83046
+ * "template_name": "recordatorio_24h",
83047
+ * "asks_confirmation": true,
83048
+ * "include_reschedule_link": true
83049
+ * },
83050
+ * {
83051
+ * "key": "h2",
83052
+ * "offset_hours": 2,
83053
+ * "channel": "whatsapp",
83054
+ * "template_name": "recordatorio_2h",
83055
+ * "asks_confirmation": false,
83056
+ * "include_reschedule_link": false
83057
+ * },
83058
+ * {
83059
+ * "key": "no_show",
83060
+ * "event": "no_show",
83061
+ * "offset_hours": 0.17,
83062
+ * "channel": "whatsapp",
83063
+ * "email": "fallback",
83064
+ * "template_name": "reagendar",
83065
+ * "asks_confirmation": false,
83066
+ * "include_reschedule_link": true
83067
+ * }
83068
+ * ],
83069
+ * "active": true,
83070
+ * "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
83071
+ * "single_sender_confirmed_by": null,
83072
+ * "vendor_sends_reminders": false,
83073
+ * "live": true,
83074
+ * "blocked_reason": null,
83075
+ * "defaults": [],
83076
+ * "updated_at": "2026-10-11T12:00:00.000Z",
83077
+ * "confirm_status_id": null,
83078
+ * "confirm_status": null,
83079
+ * "create_contacts_from_patient_phone": false
83080
+ * }
83081
+ * }
83082
+ */
83083
+ "application/json": {
83084
+ data?: unknown;
83085
+ meta?: {
83086
+ [key: string]: unknown;
83087
+ };
83088
+ };
83089
+ };
83090
+ };
83091
+ /** @description Validation error */
83092
+ 400: {
83093
+ headers: {
83094
+ [name: string]: unknown;
83095
+ };
83096
+ content: {
83097
+ "application/json": components["schemas"]["Error"];
83098
+ };
83099
+ };
83100
+ /** @description Unauthorized */
83101
+ 401: {
83102
+ headers: {
83103
+ [name: string]: unknown;
83104
+ };
83105
+ content: {
83106
+ "application/json": components["schemas"]["Error"];
83107
+ };
83108
+ };
83109
+ /** @description Forbidden */
83110
+ 403: {
83111
+ headers: {
83112
+ [name: string]: unknown;
83113
+ };
83114
+ content: {
83115
+ "application/json": components["schemas"]["Error"];
83116
+ };
83117
+ };
83118
+ /** @description Not found */
83119
+ 404: {
83120
+ headers: {
83121
+ [name: string]: unknown;
83122
+ };
83123
+ content: {
83124
+ "application/json": components["schemas"]["Error"];
83125
+ };
83126
+ };
83127
+ /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83128
+ 409: {
83129
+ headers: {
83130
+ [name: string]: unknown;
83131
+ };
83132
+ content: {
83133
+ "application/json": components["schemas"]["Error"];
83134
+ };
83135
+ };
83136
+ /** @description Rate limited */
83137
+ 429: {
83138
+ headers: {
83139
+ [name: string]: unknown;
83140
+ };
83141
+ content: {
83142
+ "application/json": components["schemas"]["Error"];
83143
+ };
83144
+ };
83145
+ };
83146
+ };
83147
+ post?: never;
83148
+ delete?: never;
83149
+ options?: never;
83150
+ head?: never;
83151
+ patch?: never;
83152
+ trace?: never;
83153
+ };
83154
+ "/appointments/reminders/plan/confirm-single-sender": {
83155
+ parameters: {
83156
+ query?: never;
83157
+ header?: never;
83158
+ path?: never;
83159
+ cookie?: never;
83160
+ };
83161
+ get?: never;
83162
+ put?: never;
83163
+ /**
83164
+ * Declare Vitrina the single sender of reminders
83165
+ * @description Records who declared that no other system reminds these people (the only thing that makes an active ladder legal). Reversible with POST /appointments/reminders/plan/revoke-single-sender.
83166
+ */
83167
+ post: {
83168
+ parameters: {
83169
+ query?: never;
83170
+ header?: {
83171
+ /** @description Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation. */
83172
+ "Idempotency-Key"?: string;
83173
+ };
83174
+ path?: never;
83175
+ cookie?: never;
83176
+ };
83177
+ requestBody?: never;
83178
+ responses: {
83179
+ /** @description Reminder plan */
83180
+ 200: {
83181
+ headers: {
83182
+ [name: string]: unknown;
83183
+ };
83184
+ content: {
83185
+ /**
83186
+ * @example {
83187
+ * "data": {
83188
+ * "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
83189
+ * "name": "Recordatorios de cita",
83190
+ * "rungs": [
83191
+ * {
83192
+ * "key": "welcome",
83193
+ * "event": "booked",
83194
+ * "offset_hours": 0,
83195
+ * "channel": "whatsapp",
83196
+ * "email": "also",
83197
+ * "template_name": null,
83198
+ * "templates": {
83199
+ * "demo": "demo_confirmada_v1"
83200
+ * },
83201
+ * "asks_confirmation": false,
83202
+ * "include_reschedule_link": true
83203
+ * },
83204
+ * {
83205
+ * "key": "h24",
83206
+ * "offset_hours": 24,
83207
+ * "channel": "whatsapp",
83208
+ * "email": "fallback",
83209
+ * "template_name": "recordatorio_24h",
83210
+ * "asks_confirmation": true,
83211
+ * "include_reschedule_link": true
83212
+ * },
83213
+ * {
83214
+ * "key": "h2",
83215
+ * "offset_hours": 2,
83216
+ * "channel": "whatsapp",
83217
+ * "template_name": "recordatorio_2h",
83218
+ * "asks_confirmation": false,
83219
+ * "include_reschedule_link": false
83220
+ * },
83221
+ * {
83222
+ * "key": "no_show",
83223
+ * "event": "no_show",
83224
+ * "offset_hours": 0.17,
83225
+ * "channel": "whatsapp",
83226
+ * "email": "fallback",
83227
+ * "template_name": "reagendar",
83228
+ * "asks_confirmation": false,
83229
+ * "include_reschedule_link": true
83230
+ * }
83231
+ * ],
83232
+ * "active": true,
83233
+ * "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
83234
+ * "single_sender_confirmed_by": null,
83235
+ * "vendor_sends_reminders": false,
83236
+ * "live": true,
83237
+ * "blocked_reason": null,
83238
+ * "defaults": [],
83239
+ * "updated_at": "2026-10-11T12:00:00.000Z",
83240
+ * "confirm_status_id": null,
83241
+ * "confirm_status": null,
83242
+ * "create_contacts_from_patient_phone": false
83243
+ * }
83244
+ * }
83245
+ */
83246
+ "application/json": {
83247
+ data?: unknown;
83248
+ meta?: {
83249
+ [key: string]: unknown;
83250
+ };
83251
+ };
83252
+ };
83253
+ };
83254
+ /** @description Validation error */
83255
+ 400: {
83256
+ headers: {
83257
+ [name: string]: unknown;
83258
+ };
83259
+ content: {
83260
+ "application/json": components["schemas"]["Error"];
83261
+ };
83262
+ };
83263
+ /** @description Unauthorized */
83264
+ 401: {
83265
+ headers: {
83266
+ [name: string]: unknown;
83267
+ };
83268
+ content: {
83269
+ "application/json": components["schemas"]["Error"];
83270
+ };
83271
+ };
83272
+ /** @description Forbidden */
83273
+ 403: {
83274
+ headers: {
83275
+ [name: string]: unknown;
83276
+ };
83277
+ content: {
83278
+ "application/json": components["schemas"]["Error"];
83279
+ };
83280
+ };
83281
+ /** @description Not found */
83282
+ 404: {
83283
+ headers: {
83284
+ [name: string]: unknown;
83285
+ };
83286
+ content: {
83287
+ "application/json": components["schemas"]["Error"];
83288
+ };
83289
+ };
83290
+ /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83291
+ 409: {
83292
+ headers: {
83293
+ [name: string]: unknown;
83294
+ };
83295
+ content: {
83296
+ "application/json": components["schemas"]["Error"];
83297
+ };
83298
+ };
83299
+ /** @description Rate limited */
83300
+ 429: {
83301
+ headers: {
83302
+ [name: string]: unknown;
83303
+ };
83304
+ content: {
83305
+ "application/json": components["schemas"]["Error"];
83306
+ };
83307
+ };
83308
+ };
83309
+ };
83310
+ delete?: never;
83311
+ options?: never;
83312
+ head?: never;
83313
+ patch?: never;
83314
+ trace?: never;
83315
+ };
83316
+ "/appointments/reminders/plan/revoke-single-sender": {
83317
+ parameters: {
83318
+ query?: never;
83319
+ header?: never;
83320
+ path?: never;
83321
+ cookie?: never;
83322
+ };
83323
+ get?: never;
83324
+ put?: never;
83325
+ /**
83326
+ * Stop sending reminders
83327
+ * @description Clears the declaration and deactivates the ladder in the same write.
83328
+ */
83329
+ post: {
83330
+ parameters: {
83331
+ query?: never;
83332
+ header?: {
83333
+ /** @description Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation. */
83334
+ "Idempotency-Key"?: string;
83335
+ };
83336
+ path?: never;
83337
+ cookie?: never;
83338
+ };
83339
+ requestBody?: never;
83340
+ responses: {
83341
+ /** @description Reminder plan */
83342
+ 200: {
83343
+ headers: {
83344
+ [name: string]: unknown;
83345
+ };
83346
+ content: {
83347
+ /**
83348
+ * @example {
83349
+ * "data": {
83350
+ * "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
83351
+ * "name": "Recordatorios de cita",
83352
+ * "rungs": [
83353
+ * {
83354
+ * "key": "welcome",
83355
+ * "event": "booked",
83356
+ * "offset_hours": 0,
83357
+ * "channel": "whatsapp",
83358
+ * "email": "also",
83359
+ * "template_name": null,
83360
+ * "templates": {
83361
+ * "demo": "demo_confirmada_v1"
83362
+ * },
83363
+ * "asks_confirmation": false,
83364
+ * "include_reschedule_link": true
83365
+ * },
83366
+ * {
83367
+ * "key": "h24",
83368
+ * "offset_hours": 24,
83369
+ * "channel": "whatsapp",
83370
+ * "email": "fallback",
83371
+ * "template_name": "recordatorio_24h",
83372
+ * "asks_confirmation": true,
83373
+ * "include_reschedule_link": true
83374
+ * },
83375
+ * {
83376
+ * "key": "h2",
83377
+ * "offset_hours": 2,
83378
+ * "channel": "whatsapp",
83379
+ * "template_name": "recordatorio_2h",
83380
+ * "asks_confirmation": false,
83381
+ * "include_reschedule_link": false
83382
+ * },
83383
+ * {
83384
+ * "key": "no_show",
83385
+ * "event": "no_show",
83386
+ * "offset_hours": 0.17,
83387
+ * "channel": "whatsapp",
83388
+ * "email": "fallback",
83389
+ * "template_name": "reagendar",
83390
+ * "asks_confirmation": false,
83391
+ * "include_reschedule_link": true
83392
+ * }
83393
+ * ],
83394
+ * "active": true,
83395
+ * "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
83396
+ * "single_sender_confirmed_by": null,
83397
+ * "vendor_sends_reminders": false,
83398
+ * "live": true,
83399
+ * "blocked_reason": null,
83400
+ * "defaults": [],
83401
+ * "updated_at": "2026-10-11T12:00:00.000Z",
83402
+ * "confirm_status_id": null,
83403
+ * "confirm_status": null,
83404
+ * "create_contacts_from_patient_phone": false
83405
+ * }
83406
+ * }
83407
+ */
83408
+ "application/json": {
83409
+ data?: unknown;
83410
+ meta?: {
83411
+ [key: string]: unknown;
83412
+ };
83413
+ };
83414
+ };
83415
+ };
83416
+ /** @description Validation error */
83417
+ 400: {
83418
+ headers: {
83419
+ [name: string]: unknown;
83420
+ };
83421
+ content: {
83422
+ "application/json": components["schemas"]["Error"];
83423
+ };
83424
+ };
83425
+ /** @description Unauthorized */
83426
+ 401: {
83427
+ headers: {
83428
+ [name: string]: unknown;
83429
+ };
83430
+ content: {
83431
+ "application/json": components["schemas"]["Error"];
83432
+ };
83433
+ };
83434
+ /** @description Forbidden */
83435
+ 403: {
83436
+ headers: {
83437
+ [name: string]: unknown;
83438
+ };
83439
+ content: {
83440
+ "application/json": components["schemas"]["Error"];
83441
+ };
83442
+ };
83443
+ /** @description Not found */
83444
+ 404: {
83445
+ headers: {
83446
+ [name: string]: unknown;
83447
+ };
83448
+ content: {
83449
+ "application/json": components["schemas"]["Error"];
83450
+ };
83451
+ };
83452
+ /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83453
+ 409: {
83454
+ headers: {
83455
+ [name: string]: unknown;
83456
+ };
83457
+ content: {
83458
+ "application/json": components["schemas"]["Error"];
83459
+ };
83460
+ };
83461
+ /** @description Rate limited */
83462
+ 429: {
83463
+ headers: {
83464
+ [name: string]: unknown;
83465
+ };
83466
+ content: {
83467
+ "application/json": components["schemas"]["Error"];
83468
+ };
83469
+ };
83470
+ };
83471
+ };
83472
+ delete?: never;
83473
+ options?: never;
83474
+ head?: never;
83475
+ patch?: never;
83476
+ trace?: never;
83477
+ };
83478
+ "/appointments/reminders": {
83479
+ parameters: {
83480
+ query?: never;
83481
+ header?: never;
83482
+ path?: never;
83483
+ cookie?: never;
83484
+ };
83485
+ /**
83486
+ * List sent appointment reminders
83487
+ * @description The send ledger: one row per appointment per rung (and per email twin, keyed `<rung>_email`) with the outcome, the reason when skipped, the manage link that went out and the person's confirm/cancel reply.
83488
+ */
83489
+ get: {
83490
+ parameters: {
83491
+ query?: {
83492
+ limit?: number;
83493
+ offset?: number | null;
83494
+ };
83495
+ header?: never;
83496
+ path?: never;
83497
+ cookie?: never;
83498
+ };
83499
+ requestBody?: never;
83500
+ responses: {
83501
+ /** @description Appointment reminders */
83502
+ 200: {
83503
+ headers: {
83504
+ [name: string]: unknown;
83505
+ };
83506
+ content: {
83507
+ /**
83508
+ * @example {
83509
+ * "data": {
83510
+ * "items": [
83511
+ * {
83512
+ * "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b11",
83513
+ * "appointment_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b12",
83514
+ * "rung_key": "welcome",
83515
+ * "offset_minutes": 0,
83516
+ * "channel": "whatsapp",
83517
+ * "asks_confirmation": false,
83518
+ * "template_name": "demo_confirmada_v1",
83519
+ * "status": "sent",
83520
+ * "reason_code": null,
83521
+ * "detail": null,
83522
+ * "conversation_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b13",
83523
+ * "sent_at": "2026-10-11T12:01:00.000Z",
83524
+ * "manage_url": null,
83525
+ * "reply_kind": null,
83526
+ * "reply_text": null,
83527
+ * "replied_at": null,
83528
+ * "applied_status_id": null,
83529
+ * "apply_error": null,
83530
+ * "created_at": "2026-10-11T12:01:00.000Z"
83531
+ * }
83532
+ * ]
83533
+ * }
83534
+ * }
83535
+ */
83536
+ "application/json": {
83537
+ data?: unknown;
83538
+ meta?: {
83539
+ [key: string]: unknown;
83540
+ };
83541
+ };
83542
+ };
83543
+ };
83544
+ /** @description Validation error */
83545
+ 400: {
83546
+ headers: {
83547
+ [name: string]: unknown;
83548
+ };
83549
+ content: {
83550
+ "application/json": components["schemas"]["Error"];
83551
+ };
83552
+ };
83553
+ /** @description Unauthorized */
83554
+ 401: {
83555
+ headers: {
83556
+ [name: string]: unknown;
83557
+ };
83558
+ content: {
83559
+ "application/json": components["schemas"]["Error"];
83560
+ };
83561
+ };
83562
+ /** @description Forbidden */
83563
+ 403: {
83564
+ headers: {
83565
+ [name: string]: unknown;
83566
+ };
83567
+ content: {
83568
+ "application/json": components["schemas"]["Error"];
83569
+ };
83570
+ };
83571
+ /** @description Not found */
83572
+ 404: {
83573
+ headers: {
83574
+ [name: string]: unknown;
83575
+ };
83576
+ content: {
83577
+ "application/json": components["schemas"]["Error"];
83578
+ };
83579
+ };
83580
+ /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83581
+ 409: {
83582
+ headers: {
83583
+ [name: string]: unknown;
83584
+ };
83585
+ content: {
83586
+ "application/json": components["schemas"]["Error"];
83587
+ };
83588
+ };
83589
+ /** @description Rate limited */
83590
+ 429: {
83591
+ headers: {
83592
+ [name: string]: unknown;
83593
+ };
83594
+ content: {
83595
+ "application/json": components["schemas"]["Error"];
83596
+ };
83597
+ };
83598
+ };
83599
+ };
83600
+ put?: never;
83601
+ post?: never;
83602
+ delete?: never;
83603
+ options?: never;
83604
+ head?: never;
83605
+ patch?: never;
83606
+ trace?: never;
83607
+ };
83608
+ "/appointment-types": {
83609
+ parameters: {
83610
+ query?: never;
83611
+ header?: never;
83612
+ path?: never;
83613
+ cookie?: never;
83614
+ };
83615
+ /**
83616
+ * List appointment types
83617
+ * @description The per-tenant bookable catalogue, which is ALSO the «Servicios» catalogue: a servicio is an appointment type with a price (there is no `/services` resource). `kind` and `active_only` are the screen’s tabs and `q` is its search box — free text over name AND description, case-insensitive, with `%` and `_` escaped so "50% off" is a literal rather than a wildcard. All three COMPOSE: `?kind=service&active_only=true&q=limpieza` is "the active services whose name or description mentions limpieza". An empty or omitted `q` is simply no text filter.
83618
+ *
83619
+ * ELIMINATED types (`deleted_at` non-null, see `DELETE /appointment-types/{id}`) are excluded from EVERY combination of these filters — including `active_only=false`, so they never come back as "inactivos". Every row returned here therefore carries `deleted_at: null`; the field is on the wire as an audit fact, not as a state to branch on.
83620
+ *
83621
+ * `price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.
83622
+ */
83623
+ get: {
83624
+ parameters: {
83625
+ query?: {
83626
+ kind?: ("test_drive" | "external" | "service")[];
83627
+ active_only?: boolean | null;
83628
+ q?: string;
83629
+ };
83630
+ header?: never;
83631
+ path?: never;
83632
+ cookie?: never;
83633
+ };
83634
+ requestBody?: never;
83635
+ responses: {
83636
+ /** @description Appointment types */
83637
+ 200: {
83638
+ headers: {
83639
+ [name: string]: unknown;
83640
+ };
83641
+ content: {
83642
+ /**
83643
+ * @example {
83644
+ * "data": [
83645
+ * {
83646
+ * "id": "eeee0000-0000-4000-8000-000000000001",
83647
+ * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
83648
+ * "name": "Consulta de evaluación",
83649
+ * "description": "Primera visita: revisión y presupuesto.",
83650
+ * "kind": "service",
83651
+ * "engine": "native",
83652
+ * "external_ref": null,
83653
+ * "buffer_minutes": null,
83654
+ * "duration_minutes": 45,
83655
+ * "price_amount": 35000,
83656
+ * "price_clp": 35000,
83657
+ * "price_currency": "CLP",
83658
+ * "price_is_from": true,
83659
+ * "eligible_staff_ids": [],
83660
+ * "is_active": true,
83661
+ * "is_default": false,
83662
+ * "deleted_at": null,
83663
+ * "created_at": "2026-09-22T12:50:27.458Z",
83664
+ * "updated_at": "2026-09-22T12:50:27.458Z"
83665
+ * },
83666
+ * {
83667
+ * "id": "eeee0000-0000-4000-8000-000000000002",
83668
+ * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
83669
+ * "name": "Test drive",
83670
+ * "description": "Prueba de manejo de 30 minutos, con ejecutivo.",
83671
+ * "kind": "test_drive",
83672
+ * "engine": "native",
83673
+ * "external_ref": null,
83674
+ * "buffer_minutes": null,
83675
+ * "duration_minutes": 30,
83676
+ * "price_amount": null,
83677
+ * "price_clp": null,
83678
+ * "price_currency": "CLP",
83679
+ * "price_is_from": false,
83680
+ * "eligible_staff_ids": [],
83681
+ * "is_active": true,
83682
+ * "is_default": true,
83683
+ * "deleted_at": null,
83684
+ * "created_at": "2026-06-02T11:02:10.004Z",
83685
+ * "updated_at": "2026-06-02T11:02:10.004Z"
83686
+ * }
83687
+ * ],
83688
+ * "meta": {
83689
+ * "total": 2
83690
+ * }
83691
+ * }
83692
+ */
83693
+ "application/json": {
83694
+ data?: unknown;
83695
+ meta?: {
83696
+ [key: string]: unknown;
83697
+ };
83698
+ };
83699
+ };
83700
+ };
83701
+ /** @description Validation error */
83702
+ 400: {
83703
+ headers: {
83704
+ [name: string]: unknown;
83705
+ };
83706
+ content: {
83707
+ "application/json": components["schemas"]["Error"];
83708
+ };
83709
+ };
83710
+ /** @description Unauthorized */
83711
+ 401: {
83712
+ headers: {
83713
+ [name: string]: unknown;
83714
+ };
83715
+ content: {
83716
+ "application/json": components["schemas"]["Error"];
83717
+ };
83718
+ };
83719
+ /** @description Forbidden */
83720
+ 403: {
83721
+ headers: {
83722
+ [name: string]: unknown;
83723
+ };
83724
+ content: {
83725
+ "application/json": components["schemas"]["Error"];
83726
+ };
83727
+ };
83728
+ /** @description Not found */
83729
+ 404: {
83730
+ headers: {
83731
+ [name: string]: unknown;
83732
+ };
83733
+ content: {
83734
+ "application/json": components["schemas"]["Error"];
83735
+ };
83736
+ };
83737
+ /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83738
+ 409: {
83739
+ headers: {
83740
+ [name: string]: unknown;
83741
+ };
83742
+ content: {
83743
+ "application/json": components["schemas"]["Error"];
83744
+ };
83745
+ };
83746
+ /** @description Rate limited */
83747
+ 429: {
83748
+ headers: {
83749
+ [name: string]: unknown;
83750
+ };
83751
+ content: {
83752
+ "application/json": components["schemas"]["Error"];
83753
+ };
83754
+ };
83755
+ };
83756
+ };
83757
+ put?: never;
83758
+ /**
83759
+ * Create an appointment type
83760
+ * @description Adds a bookable type to the catalogue — a servicio once it carries a price. `duration_minutes` is what sets the slot LENGTH when this type is booked, while the agenda grid stays `slot_minutes` (`GET /appointments/config`).
83761
+ *
83762
+ * `eligible_staff_ids` narrows who may take it: booking assigns round-robin from that pool, and an empty list means the workspace’s whole sales/professional team. `kind` defaults to `external`.
83763
+ *
83764
+ * The NAME is unique among the live rows, so re-creating a type you eliminated is allowed and yields a NEW id.
83765
+ *
83766
+ * `price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.
83767
+ *
83768
+ * **Depósito / abono.** The deposit rule for this service is NOT on this resource: it is a `deposit_policy` row scoped to `appointment_type`, read and written through `GET|PUT /deposit-policies`. Each booking still takes a SNAPSHOT — the resolved amount and deadline land on the cita’s own `customer_obligation` — so editing the policy never changes what an already-booked customer was told to transfer.
83769
+ */
83770
+ post: {
83771
+ parameters: {
83772
+ query?: never;
83773
+ header?: {
83774
+ /** @description Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation. */
83775
+ "Idempotency-Key"?: string;
83776
+ };
83777
+ path?: never;
83778
+ cookie?: never;
83779
+ };
83780
+ requestBody?: {
83781
+ content: {
83782
+ /**
83783
+ * @example {
83784
+ * "name": "Consulta de evaluación",
83785
+ * "description": "Primera visita: revisión y presupuesto.",
83786
+ * "kind": "service",
83787
+ * "duration_minutes": 45,
83788
+ * "price_amount": 35000,
83789
+ * "price_currency": "CLP",
83790
+ * "price_is_from": true
83791
+ * }
83792
+ */
83793
+ "application/json": {
83794
+ name: string;
83795
+ description?: string | null;
83796
+ /** @enum {string} */
83797
+ kind?: "test_drive" | "external" | "service";
83798
+ duration_minutes: number;
83799
+ price_amount?: number | null;
83800
+ price_currency?: string;
83801
+ price_is_from?: boolean;
83802
+ eligible_staff_ids?: string[];
83803
+ is_active?: boolean;
83804
+ };
83805
+ };
83806
+ };
83807
+ responses: {
83808
+ /** @description Appointment type */
83809
+ 201: {
83810
+ headers: {
83811
+ [name: string]: unknown;
83812
+ };
83813
+ content: {
83814
+ /**
83815
+ * @example {
83816
+ * "id": "eeee0000-0000-4000-8000-000000000001",
83817
+ * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
83818
+ * "name": "Consulta de evaluación",
83819
+ * "description": "Primera visita: revisión y presupuesto.",
83820
+ * "kind": "service",
83821
+ * "engine": "native",
83822
+ * "external_ref": null,
83823
+ * "buffer_minutes": null,
83824
+ * "duration_minutes": 45,
83825
+ * "price_amount": 35000,
83826
+ * "price_clp": 35000,
83827
+ * "price_currency": "CLP",
83828
+ * "price_is_from": true,
83829
+ * "eligible_staff_ids": [],
83830
+ * "is_active": true,
83831
+ * "is_default": false,
83832
+ * "deleted_at": null,
83833
+ * "created_at": "2026-09-22T12:50:27.458Z",
83834
+ * "updated_at": "2026-09-22T12:50:27.458Z"
83835
+ * }
83836
+ */
83837
+ "application/json": {
83838
+ data?: unknown;
83839
+ };
83840
+ };
83841
+ };
83842
+ /** @description Validation error */
83843
+ 400: {
83844
+ headers: {
83845
+ [name: string]: unknown;
83846
+ };
83847
+ content: {
83848
+ "application/json": components["schemas"]["Error"];
83849
+ };
83850
+ };
83851
+ /** @description Unauthorized */
83852
+ 401: {
83853
+ headers: {
83854
+ [name: string]: unknown;
83855
+ };
83856
+ content: {
83857
+ "application/json": components["schemas"]["Error"];
83858
+ };
83859
+ };
83860
+ /** @description Forbidden */
83861
+ 403: {
83862
+ headers: {
83863
+ [name: string]: unknown;
83864
+ };
83865
+ content: {
83866
+ "application/json": components["schemas"]["Error"];
83867
+ };
83868
+ };
83869
+ /** @description Not found */
83870
+ 404: {
83871
+ headers: {
83872
+ [name: string]: unknown;
83873
+ };
83874
+ content: {
83875
+ "application/json": components["schemas"]["Error"];
83876
+ };
83877
+ };
83878
+ /** @description Conflict (incl. Idempotency-Key reuse with different body) */
83879
+ 409: {
83880
+ headers: {
83881
+ [name: string]: unknown;
83882
+ };
83883
+ content: {
83884
+ "application/json": components["schemas"]["Error"];
83885
+ };
83886
+ };
83887
+ /** @description Rate limited */
83888
+ 429: {
83889
+ headers: {
83890
+ [name: string]: unknown;
83891
+ };
83892
+ content: {
83893
+ "application/json": components["schemas"]["Error"];
83894
+ };
83895
+ };
83896
+ };
83897
+ };
83898
+ delete?: never;
83899
+ options?: never;
83900
+ head?: never;
83901
+ patch?: never;
83902
+ trace?: never;
83903
+ };
83904
+ "/appointment-types/{id}": {
83905
+ parameters: {
83906
+ query?: never;
83907
+ header?: never;
83908
+ path?: never;
83909
+ cookie?: never;
83910
+ };
83911
+ /**
83912
+ * Get an appointment type
83913
+ * @description By uuid. An ELIMINATED type is a 404 here even though the appointments booked against it still resolve its name — the catalogue and the history answer different questions.
83914
+ */
83915
+ get: {
83916
+ parameters: {
83917
+ query?: never;
83918
+ header?: never;
83919
+ path: {
83920
+ id: string;
83921
+ };
83922
+ cookie?: never;
83923
+ };
83924
+ requestBody?: never;
83925
+ responses: {
83926
+ /** @description Appointment type */
83927
+ 200: {
83928
+ headers: {
83929
+ [name: string]: unknown;
83930
+ };
83931
+ content: {
83932
+ /**
83933
+ * @example {
83934
+ * "id": "eeee0000-0000-4000-8000-000000000001",
83935
+ * "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
83936
+ * "name": "Consulta de evaluación",
83937
+ * "description": "Primera visita: revisión y presupuesto.",
83938
+ * "kind": "service",
83939
+ * "engine": "native",
83940
+ * "external_ref": null,
83941
+ * "buffer_minutes": null,
83942
+ * "duration_minutes": 45,
83943
+ * "price_amount": 35000,
83944
+ * "price_clp": 35000,
83945
+ * "price_currency": "CLP",
83946
+ * "price_is_from": true,
83947
+ * "eligible_staff_ids": [],
83948
+ * "is_active": true,
83949
+ * "is_default": false,
83950
+ * "deleted_at": null,
83951
+ * "created_at": "2026-09-22T12:50:27.458Z",
83952
+ * "updated_at": "2026-09-22T12:50:27.458Z"
83096
83953
  * }
83097
83954
  */
83098
83955
  "application/json": {
@@ -140052,6 +140909,219 @@ interface paths {
140052
140909
  patch?: never;
140053
140910
  trace?: never;
140054
140911
  };
140912
+ "/ads/paid-funnel": {
140913
+ parameters: {
140914
+ query?: never;
140915
+ header?: never;
140916
+ path?: never;
140917
+ cookie?: never;
140918
+ };
140919
+ /**
140920
+ * Paid funnel: spend, leads, qualified, booked, held and paid per arm, campaign, ad set and ad
140921
+ * @description The scoreboard that judges a paid test on cost per demo HELD. Per row: people at each step (lead → ICP-qualified → demo booked → demo held → first paid invoice), the cost of each step, contacted within 24 h, lead-to-booked, show rate and qualified share, for one destination `arm` (`whatsapp`, `instant_form`, `website`, `other`; an ad with spend and nobody is `unassigned`). Steps are read from the outcome ledger the Meta events leave from, so a column equals the events sent for the same period (`basis=event`; `reconciliation.events` prints them). People with no id at the level sit on the arm’s `unattributed` row, so rows sum to `totals`; `reconciliation.spend_matches` checks Σ spend against `/ads/overview`. A failed read is `null`, never 0.
140922
+ */
140923
+ get: {
140924
+ parameters: {
140925
+ query: {
140926
+ /** @description Window start (inclusive), YYYY-MM-DD, Santiago. */
140927
+ from: string;
140928
+ /** @description Window end (inclusive), YYYY-MM-DD, Santiago. */
140929
+ to: string;
140930
+ /** @description `arm` (WhatsApp / Instant Form / website), `campaign`, `ad_set` or `ad`. `arms` is always returned whatever the level. */
140931
+ level?: "arm" | "campaign" | "ad_set" | "ad";
140932
+ /** @description `cohort` (default): the people whose lead fell in the window, every later step counted whenever it happened. `event`: every column is the number of people whose event of that step fell in the window, so each equals the events sent to Meta for the same period. */
140933
+ basis?: "cohort" | "event";
140934
+ /** @description The attribution model for the spend read. Default `last_touch`. */
140935
+ model?: string;
140936
+ };
140937
+ header?: never;
140938
+ path?: never;
140939
+ cookie?: never;
140940
+ };
140941
+ requestBody?: never;
140942
+ responses: {
140943
+ /** @description The funnel at the requested level */
140944
+ 200: {
140945
+ headers: {
140946
+ [name: string]: unknown;
140947
+ };
140948
+ content: {
140949
+ /**
140950
+ * @example {
140951
+ * "data": {
140952
+ * "level": "arm",
140953
+ * "basis": "cohort",
140954
+ * "window": {
140955
+ * "from": "2026-11-16",
140956
+ * "to": "2026-11-29"
140957
+ * },
140958
+ * "rows": [
140959
+ * {
140960
+ * "arm": "whatsapp",
140961
+ * "external_id": null,
140962
+ * "name": null,
140963
+ * "parent": null,
140964
+ * "unattributed": false,
140965
+ * "leads": 100,
140966
+ * "qualified": 50,
140967
+ * "booked": 25,
140968
+ * "held": 12,
140969
+ * "paid": 0,
140970
+ * "contacted_24h": 90,
140971
+ * "spend": 1400000,
140972
+ * "costs": {
140973
+ * "per_lead": 14000,
140974
+ * "per_qualified": 28000,
140975
+ * "per_booked": 56000,
140976
+ * "per_held": 112000,
140977
+ * "per_paid": null
140978
+ * },
140979
+ * "rates": {
140980
+ * "qualified_share": 0.5,
140981
+ * "contacted_24h_rate": 0.9,
140982
+ * "lead_to_booked": 0.25,
140983
+ * "show_rate": 0.5
140984
+ * }
140985
+ * }
140986
+ * ],
140987
+ * "arms": [],
140988
+ * "totals": {
140989
+ * "leads": 100,
140990
+ * "qualified": 50,
140991
+ * "booked": 25,
140992
+ * "held": 12,
140993
+ * "paid": 0,
140994
+ * "contacted_24h": 90,
140995
+ * "spend": 1400000,
140996
+ * "costs": {
140997
+ * "per_lead": 14000,
140998
+ * "per_qualified": 28000,
140999
+ * "per_booked": 56000,
141000
+ * "per_held": 112000,
141001
+ * "per_paid": null
141002
+ * },
141003
+ * "rates": {
141004
+ * "qualified_share": 0.5,
141005
+ * "contacted_24h_rate": 0.9,
141006
+ * "lead_to_booked": 0.25,
141007
+ * "show_rate": 0.5
141008
+ * }
141009
+ * },
141010
+ * "reconciliation": {
141011
+ * "overview_spend": 1400000,
141012
+ * "spend_matches": true,
141013
+ * "events": null,
141014
+ * "counts_match": null,
141015
+ * "leads_without_ad": 0
141016
+ * }
141017
+ * }
141018
+ * }
141019
+ */
141020
+ "application/json": {
141021
+ data: components["schemas"]["AdsDemoFunnel"];
141022
+ };
141023
+ };
141024
+ };
141025
+ /** @description Validation error */
141026
+ 400: {
141027
+ headers: {
141028
+ [name: string]: unknown;
141029
+ };
141030
+ content: {
141031
+ "application/json": components["schemas"]["Error"];
141032
+ };
141033
+ };
141034
+ /** @description Unauthorized */
141035
+ 401: {
141036
+ headers: {
141037
+ [name: string]: unknown;
141038
+ };
141039
+ content: {
141040
+ "application/json": components["schemas"]["Error"];
141041
+ };
141042
+ };
141043
+ /** @description Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. */
141044
+ 402: {
141045
+ headers: {
141046
+ [name: string]: unknown;
141047
+ };
141048
+ content: {
141049
+ /**
141050
+ * @example {
141051
+ * "error": {
141052
+ * "code": "ENTITLEMENT_NOT_ACTIVE",
141053
+ * "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
141054
+ * "details": {
141055
+ * "required": [
141056
+ * "vitrina_ads"
141057
+ * ],
141058
+ * "active": [],
141059
+ * "feature": "vitrina_ads",
141060
+ * "entitlement_state": "off"
141061
+ * }
141062
+ * }
141063
+ * }
141064
+ */
141065
+ "application/json": components["schemas"]["Error"];
141066
+ };
141067
+ };
141068
+ /** @description Forbidden */
141069
+ 403: {
141070
+ headers: {
141071
+ [name: string]: unknown;
141072
+ };
141073
+ content: {
141074
+ "application/json": components["schemas"]["Error"];
141075
+ };
141076
+ };
141077
+ /** @description Not found */
141078
+ 404: {
141079
+ headers: {
141080
+ [name: string]: unknown;
141081
+ };
141082
+ content: {
141083
+ "application/json": components["schemas"]["Error"];
141084
+ };
141085
+ };
141086
+ /** @description Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write. */
141087
+ 409: {
141088
+ headers: {
141089
+ [name: string]: unknown;
141090
+ };
141091
+ content: {
141092
+ /**
141093
+ * @example {
141094
+ * "error": {
141095
+ * "code": "ADS_KEY_NEEDS_REMINT",
141096
+ * "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
141097
+ * "details": {
141098
+ * "key_kind": "core"
141099
+ * }
141100
+ * }
141101
+ * }
141102
+ */
141103
+ "application/json": components["schemas"]["Error"];
141104
+ };
141105
+ };
141106
+ /** @description Rate limited */
141107
+ 429: {
141108
+ headers: {
141109
+ [name: string]: unknown;
141110
+ };
141111
+ content: {
141112
+ "application/json": components["schemas"]["Error"];
141113
+ };
141114
+ };
141115
+ };
141116
+ };
141117
+ put?: never;
141118
+ post?: never;
141119
+ delete?: never;
141120
+ options?: never;
141121
+ head?: never;
141122
+ patch?: never;
141123
+ trace?: never;
141124
+ };
140055
141125
  "/ads/overview": {
140056
141126
  parameters: {
140057
141127
  query?: never;
@@ -145230,6 +146300,43 @@ interface paths {
145230
146300
  * "dismissed_until": null
145231
146301
  * },
145232
146302
  * {
146303
+ * "key": "budget_change:120210000000000002:2026-09-23",
146304
+ * "kind": "budget_change",
146305
+ * "screen": [
146306
+ * "campanas",
146307
+ * "resumen"
146308
+ * ],
146309
+ * "primary": false,
146310
+ * "title": "Bajar 20 % el presupuesto de la campaña «Blanqueamiento · Octubre»",
146311
+ * "why": "Se lleva el **46 %** del gasto de tus campañas (**$412.000** en septiembre) y agenda el **2,1 %** (3 de 140); «Implantes · Septiembre» agenda el **9,8 %** (12 de 122). La campaña reparte su presupuesto sola, así que se baja ahí: de **$25.000** a **$20.000** al día; 20 % es el paso máximo, y el siguiente, en 3 días.",
146312
+ * "effect": "El presupuesto diario de la campaña baja 20 %; puedes deshacerlo.",
146313
+ * "target": {
146314
+ * "level": "campaign",
146315
+ * "external_id": "120210000000000002",
146316
+ * "name": "Blanqueamiento · Octubre",
146317
+ * "campaign_external_id": null,
146318
+ * "campaign": {
146319
+ * "external_id": "120210000000000002",
146320
+ * "name": "Blanqueamiento · Octubre"
146321
+ * }
146322
+ * },
146323
+ * "params": {
146324
+ * "budget_change_pct": -20,
146325
+ * "budget_level": "campaign",
146326
+ * "budget_type": "daily",
146327
+ * "daily_budget": 25000,
146328
+ * "new_daily_budget": 20000,
146329
+ * "step_pct": 20
146330
+ * },
146331
+ * "reversible": true,
146332
+ * "risk": "reversible",
146333
+ * "engine_ref": {
146334
+ * "op": "campaigns.budget"
146335
+ * },
146336
+ * "facts": [],
146337
+ * "dismissed_until": null
146338
+ * },
146339
+ * {
145233
146340
  * "key": "nav:cheapest:120210000000000002:2026-09-23",
145234
146341
  * "kind": "nav",
145235
146342
  * "screen": [
@@ -149431,7 +150538,10 @@ interface paths {
149431
150538
  * "data": {
149432
150539
  * "offer": {
149433
150540
  * "available": false,
149434
- * "reason": "already_open"
150541
+ * "reason": "already_open",
150542
+ * "suggested_test_event": "appointment_booked",
150543
+ * "weekly_first_payments": 12,
150544
+ * "first_payment_threshold": 50
149435
150545
  * },
149436
150546
  * "experiments": [
149437
150547
  * {
@@ -149579,7 +150689,7 @@ interface paths {
149579
150689
  put?: never;
149580
150690
  /**
149581
150691
  * Record an optimisation experiment (draft)
149582
- * @description Records the control (today’s «conversaciones» ad sets) and the test arm (ad sets optimising on `test_event`, default the first payment). No Meta write: Meta’s API cannot re-optimise a published click-to-WhatsApp ad set on a purchase (the Sales objective belongs to the campaign and a published ad set’s optimisation is frozen), so the test arm is built in Ads Manager and its ids recorded here (now or with PATCH). 409 when not offered (`details.reason`).
150692
+ * @description Records the control (today’s «conversaciones» ad sets) and the test arm (ad sets optimising on `test_event`; default, D81: the offer’s `suggested_test_event` — «cita agendada» until ~50 first payments a week, then the first payment). No Meta write: Meta’s API cannot re-optimise a published click-to-WhatsApp ad set on a purchase (the Sales objective belongs to the campaign and a published ad set’s optimisation is frozen), so the test arm is built in Ads Manager and its ids recorded here (now or with PATCH). 409 when not offered (`details.reason`).
149583
150693
  */
149584
150694
  post: {
149585
150695
  parameters: {
@@ -150133,108 +151243,866 @@ interface paths {
150133
151243
  };
150134
151244
  };
150135
151245
  };
150136
- put?: never;
151246
+ put?: never;
151247
+ post?: never;
151248
+ delete?: never;
151249
+ options?: never;
151250
+ head?: never;
151251
+ /**
151252
+ * Record an arm’s ad sets, start, end or read early
151253
+ * @description `arm` records the ad sets (and campaign) an arm runs on, as built in Ads Manager — their learning read restarts. `status: running` once the test arm is live (reads learning right away); `ended` closes it and keeps the record. `read_from` compares from a day before every arm left learning (the readout then says `read_early: true`).
151254
+ */
151255
+ patch: {
151256
+ parameters: {
151257
+ query?: never;
151258
+ header?: never;
151259
+ path: {
151260
+ id: string;
151261
+ };
151262
+ cookie?: never;
151263
+ };
151264
+ requestBody?: {
151265
+ content: {
151266
+ /**
151267
+ * @example {
151268
+ * "arm": {
151269
+ * "arm_key": "first_payment",
151270
+ * "ad_sets": [
151271
+ * "120211000000000301"
151272
+ * ],
151273
+ * "campaign": "120211000000000300"
151274
+ * },
151275
+ * "status": "running"
151276
+ * }
151277
+ */
151278
+ "application/json": {
151279
+ arm?: {
151280
+ arm_key: string;
151281
+ ad_sets?: string[];
151282
+ campaign?: string | null;
151283
+ budget_share?: number | null;
151284
+ };
151285
+ /**
151286
+ * @description `running` once the test arm is live in Meta (needs its ad sets); `ended` closes it (the record is kept).
151287
+ * @enum {string}
151288
+ */
151289
+ status?: "running" | "ended";
151290
+ /** @description Compare from this clinic day even if an arm is still learning (the readout then says `read_early: true`); `null` = wait for learning. */
151291
+ read_from?: string | null;
151292
+ };
151293
+ };
151294
+ };
151295
+ responses: {
151296
+ /** @description The experiment after the change. */
151297
+ 200: {
151298
+ headers: {
151299
+ [name: string]: unknown;
151300
+ };
151301
+ content: {
151302
+ /**
151303
+ * @example {
151304
+ * "data": {
151305
+ * "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
151306
+ * "name": "Conversaciones vs primer pago",
151307
+ * "status": "running",
151308
+ * "started_at": "2026-10-02T13:00:00.000Z",
151309
+ * "ended_at": null,
151310
+ * "readout_from": null,
151311
+ * "read_early": false,
151312
+ * "created_at": "2026-10-01T21:10:00.000Z",
151313
+ * "arms": [
151314
+ * {
151315
+ * "arm_key": "control",
151316
+ * "role": "control",
151317
+ * "optimization_event": "conversations",
151318
+ * "meta_campaign": null,
151319
+ * "meta_ad_sets": [
151320
+ * "120211000000000201"
151321
+ * ],
151322
+ * "budget_share": 50,
151323
+ * "learning_status": "active",
151324
+ * "learning_checked_at": "2026-10-03T12:00:00.000Z",
151325
+ * "left_learning_at": "2026-10-02T18:00:00.000Z"
151326
+ * },
151327
+ * {
151328
+ * "arm_key": "first_payment",
151329
+ * "role": "test",
151330
+ * "optimization_event": "first_payment",
151331
+ * "meta_campaign": "120211000000000300",
151332
+ * "meta_ad_sets": [
151333
+ * "120211000000000301"
151334
+ * ],
151335
+ * "budget_share": 50,
151336
+ * "learning_status": "learning",
151337
+ * "learning_checked_at": "2026-10-03T12:00:00.000Z",
151338
+ * "left_learning_at": null
151339
+ * }
151340
+ * ]
151341
+ * }
151342
+ * }
151343
+ */
151344
+ "application/json": {
151345
+ data: components["schemas"]["AdsExperiment"];
151346
+ };
151347
+ };
151348
+ };
151349
+ /** @description Validation error */
151350
+ 400: {
151351
+ headers: {
151352
+ [name: string]: unknown;
151353
+ };
151354
+ content: {
151355
+ "application/json": components["schemas"]["Error"];
151356
+ };
151357
+ };
151358
+ /** @description Unauthorized */
151359
+ 401: {
151360
+ headers: {
151361
+ [name: string]: unknown;
151362
+ };
151363
+ content: {
151364
+ "application/json": components["schemas"]["Error"];
151365
+ };
151366
+ };
151367
+ /** @description Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. */
151368
+ 402: {
151369
+ headers: {
151370
+ [name: string]: unknown;
151371
+ };
151372
+ content: {
151373
+ /**
151374
+ * @example {
151375
+ * "error": {
151376
+ * "code": "ENTITLEMENT_NOT_ACTIVE",
151377
+ * "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
151378
+ * "details": {
151379
+ * "required": [
151380
+ * "vitrina_ads"
151381
+ * ],
151382
+ * "active": [],
151383
+ * "feature": "vitrina_ads",
151384
+ * "entitlement_state": "off"
151385
+ * }
151386
+ * }
151387
+ * }
151388
+ */
151389
+ "application/json": components["schemas"]["Error"];
151390
+ };
151391
+ };
151392
+ /** @description Forbidden */
151393
+ 403: {
151394
+ headers: {
151395
+ [name: string]: unknown;
151396
+ };
151397
+ content: {
151398
+ "application/json": components["schemas"]["Error"];
151399
+ };
151400
+ };
151401
+ /** @description Not found */
151402
+ 404: {
151403
+ headers: {
151404
+ [name: string]: unknown;
151405
+ };
151406
+ content: {
151407
+ "application/json": components["schemas"]["Error"];
151408
+ };
151409
+ };
151410
+ /** @description Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write. */
151411
+ 409: {
151412
+ headers: {
151413
+ [name: string]: unknown;
151414
+ };
151415
+ content: {
151416
+ /**
151417
+ * @example {
151418
+ * "error": {
151419
+ * "code": "ADS_KEY_NEEDS_REMINT",
151420
+ * "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
151421
+ * "details": {
151422
+ * "key_kind": "core"
151423
+ * }
151424
+ * }
151425
+ * }
151426
+ */
151427
+ "application/json": components["schemas"]["Error"];
151428
+ };
151429
+ };
151430
+ /** @description Rate limited */
151431
+ 429: {
151432
+ headers: {
151433
+ [name: string]: unknown;
151434
+ };
151435
+ content: {
151436
+ "application/json": components["schemas"]["Error"];
151437
+ };
151438
+ };
151439
+ };
151440
+ };
151441
+ trace?: never;
151442
+ };
151443
+ "/ads/experiments/{id}/readout": {
151444
+ parameters: {
151445
+ query?: never;
151446
+ header?: never;
151447
+ path?: never;
151448
+ cookie?: never;
151449
+ };
151450
+ /**
151451
+ * The experiment readout, per arm
151452
+ * @description Per arm: spend, people whose WhatsApp conversation the arm’s ads opened, «pacientes nuevos que pagaron» (a first payment), accepted presupuestos, and the two costs — cost per paying patient and cost per accepted presupuesto — side by side with the days of data. No winner and no probability. Numbers appear from the day the last arm left Meta’s learning phase (`phase: learning` until then), or from the day the owner chose to read early (`read_early: true`).
151453
+ */
151454
+ get: {
151455
+ parameters: {
151456
+ query?: never;
151457
+ header?: never;
151458
+ path: {
151459
+ id: string;
151460
+ };
151461
+ cookie?: never;
151462
+ };
151463
+ requestBody?: never;
151464
+ responses: {
151465
+ /** @description The readout. */
151466
+ 200: {
151467
+ headers: {
151468
+ [name: string]: unknown;
151469
+ };
151470
+ content: {
151471
+ /**
151472
+ * @example {
151473
+ * "data": {
151474
+ * "experiment": {
151475
+ * "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
151476
+ * "name": "Conversaciones vs primer pago",
151477
+ * "status": "running",
151478
+ * "started_at": "2026-10-02T13:00:00.000Z",
151479
+ * "ended_at": null,
151480
+ * "readout_from": null,
151481
+ * "read_early": false,
151482
+ * "created_at": "2026-10-01T21:10:00.000Z",
151483
+ * "arms": [
151484
+ * {
151485
+ * "arm_key": "control",
151486
+ * "role": "control",
151487
+ * "optimization_event": "conversations",
151488
+ * "meta_campaign": null,
151489
+ * "meta_ad_sets": [
151490
+ * "120211000000000201"
151491
+ * ],
151492
+ * "budget_share": 50,
151493
+ * "learning_status": "active",
151494
+ * "learning_checked_at": "2026-10-03T12:00:00.000Z",
151495
+ * "left_learning_at": "2026-10-02T18:00:00.000Z"
151496
+ * },
151497
+ * {
151498
+ * "arm_key": "first_payment",
151499
+ * "role": "test",
151500
+ * "optimization_event": "first_payment",
151501
+ * "meta_campaign": "120211000000000300",
151502
+ * "meta_ad_sets": [
151503
+ * "120211000000000301"
151504
+ * ],
151505
+ * "budget_share": 50,
151506
+ * "learning_status": "learning",
151507
+ * "learning_checked_at": "2026-10-03T12:00:00.000Z",
151508
+ * "left_learning_at": null
151509
+ * }
151510
+ * ]
151511
+ * },
151512
+ * "readout": {
151513
+ * "phase": "comparing",
151514
+ * "from": "2026-10-09",
151515
+ * "to": "2026-10-30",
151516
+ * "days_of_data": 22,
151517
+ * "day_number": 29,
151518
+ * "planned_days": 28,
151519
+ * "learning_finished": true,
151520
+ * "read_early": false,
151521
+ * "planned_end_from": "2026-10-30",
151522
+ * "planned_end_to": "2026-11-13",
151523
+ * "arms": [
151524
+ * {
151525
+ * "arm_key": "control",
151526
+ * "role": "control",
151527
+ * "optimization_event": "conversations",
151528
+ * "ad_set_count": 1,
151529
+ * "learning_status": "active",
151530
+ * "left_learning_at": "2026-10-02T18:00:00.000Z",
151531
+ * "spend_clp": 650000,
151532
+ * "people": 210,
151533
+ * "paying_patients": 6,
151534
+ * "accepted_quotes": 9,
151535
+ * "attended": 14,
151536
+ * "cost_per_paying_patient": 108333,
151537
+ * "cost_per_accepted_quote": 72222,
151538
+ * "sales_objective": false
151539
+ * },
151540
+ * {
151541
+ * "arm_key": "first_payment",
151542
+ * "role": "test",
151543
+ * "optimization_event": "first_payment",
151544
+ * "ad_set_count": 1,
151545
+ * "learning_status": "learning_limited",
151546
+ * "left_learning_at": "2026-10-09T12:00:00.000Z",
151547
+ * "spend_clp": 640000,
151548
+ * "people": 150,
151549
+ * "paying_patients": 8,
151550
+ * "accepted_quotes": 10,
151551
+ * "attended": 13,
151552
+ * "cost_per_paying_patient": 80000,
151553
+ * "cost_per_accepted_quote": 64000,
151554
+ * "sales_objective": true
151555
+ * }
151556
+ * ]
151557
+ * }
151558
+ * }
151559
+ * }
151560
+ */
151561
+ "application/json": {
151562
+ data: {
151563
+ experiment: components["schemas"]["AdsExperiment"];
151564
+ readout: {
151565
+ /**
151566
+ * @description `setup` (test arm not recorded) → `learning` (an arm is still learning; no numbers) → `comparing` → `ended`.
151567
+ * @enum {string}
151568
+ */
151569
+ phase: "setup" | "learning" | "comparing" | "ended";
151570
+ from: string | null;
151571
+ to: string;
151572
+ days_of_data: number;
151573
+ /** @description «Día N de 28»: days since the start, 1 on the start day, while the test runs; `null` before it starts and once it ended. */
151574
+ day_number: number | null;
151575
+ /** @description The planned length: 28. */
151576
+ planned_days: number;
151577
+ learning_finished: boolean;
151578
+ read_early: boolean;
151579
+ /** @description 4 weeks after the start. */
151580
+ planned_end_from: string | null;
151581
+ /** @description 6 weeks after the start. */
151582
+ planned_end_to: string | null;
151583
+ arms: {
151584
+ arm_key: string;
151585
+ /** @enum {string} */
151586
+ role: "control" | "test";
151587
+ /**
151588
+ * @description What the arm’s ad sets optimise for: `conversations` (Meta’s own «conversaciones», the control) or a stage Vitrina sends to Meta (`first_payment` = «primer pago», `closed_won` = «presupuesto aceptado», `appointment_attended` = «cita atendida»).
151589
+ * @enum {string}
151590
+ */
151591
+ optimization_event: "conversations" | "first_payment" | "closed_won" | "appointment_attended" | "appointment_booked" | "lead_created";
151592
+ ad_set_count: number;
151593
+ /**
151594
+ * @description Meta’s learning stage for the arm’s ad sets, READ from Meta (never guessed): `learning` (Aprendiendo), `learning_limited` (Aprendizaje limitado — too few events to stabilise), `active` (out of learning), `unknown` until a read answers. The least advanced ad set decides.
151595
+ * @enum {string}
151596
+ */
151597
+ learning_status: "learning" | "learning_limited" | "active" | "unknown";
151598
+ left_learning_at: string | null;
151599
+ /** @description Spend over the window, CLP; `null` = unknown or not started. */
151600
+ spend_clp: number | null;
151601
+ /** @description People whose WhatsApp conversation one of the arm’s ads opened in the window («personas»). */
151602
+ people: number;
151603
+ /** @description Of them, with a first payment recorded afterwards («pacientes nuevos que pagaron»). */
151604
+ paying_patients: number;
151605
+ accepted_quotes: number;
151606
+ attended: number;
151607
+ /** @description spend / paying_patients, CLP; `null` with none (never 0). */
151608
+ cost_per_paying_patient: number | null;
151609
+ cost_per_accepted_quote: number | null;
151610
+ /** @description The arm runs under a Sales objective (required to optimise click-to-WhatsApp ads on the first payment); `null` = the engine does not report the objective. */
151611
+ sales_objective: boolean | null;
151612
+ }[];
151613
+ };
151614
+ };
151615
+ };
151616
+ };
151617
+ };
151618
+ /** @description Validation error */
151619
+ 400: {
151620
+ headers: {
151621
+ [name: string]: unknown;
151622
+ };
151623
+ content: {
151624
+ "application/json": components["schemas"]["Error"];
151625
+ };
151626
+ };
151627
+ /** @description Unauthorized */
151628
+ 401: {
151629
+ headers: {
151630
+ [name: string]: unknown;
151631
+ };
151632
+ content: {
151633
+ "application/json": components["schemas"]["Error"];
151634
+ };
151635
+ };
151636
+ /** @description Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. */
151637
+ 402: {
151638
+ headers: {
151639
+ [name: string]: unknown;
151640
+ };
151641
+ content: {
151642
+ /**
151643
+ * @example {
151644
+ * "error": {
151645
+ * "code": "ENTITLEMENT_NOT_ACTIVE",
151646
+ * "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
151647
+ * "details": {
151648
+ * "required": [
151649
+ * "vitrina_ads"
151650
+ * ],
151651
+ * "active": [],
151652
+ * "feature": "vitrina_ads",
151653
+ * "entitlement_state": "off"
151654
+ * }
151655
+ * }
151656
+ * }
151657
+ */
151658
+ "application/json": components["schemas"]["Error"];
151659
+ };
151660
+ };
151661
+ /** @description Forbidden */
151662
+ 403: {
151663
+ headers: {
151664
+ [name: string]: unknown;
151665
+ };
151666
+ content: {
151667
+ "application/json": components["schemas"]["Error"];
151668
+ };
151669
+ };
151670
+ /** @description Not found */
151671
+ 404: {
151672
+ headers: {
151673
+ [name: string]: unknown;
151674
+ };
151675
+ content: {
151676
+ "application/json": components["schemas"]["Error"];
151677
+ };
151678
+ };
151679
+ /** @description Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write. */
151680
+ 409: {
151681
+ headers: {
151682
+ [name: string]: unknown;
151683
+ };
151684
+ content: {
151685
+ /**
151686
+ * @example {
151687
+ * "error": {
151688
+ * "code": "ADS_KEY_NEEDS_REMINT",
151689
+ * "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
151690
+ * "details": {
151691
+ * "key_kind": "core"
151692
+ * }
151693
+ * }
151694
+ * }
151695
+ */
151696
+ "application/json": components["schemas"]["Error"];
151697
+ };
151698
+ };
151699
+ /** @description Rate limited */
151700
+ 429: {
151701
+ headers: {
151702
+ [name: string]: unknown;
151703
+ };
151704
+ content: {
151705
+ "application/json": components["schemas"]["Error"];
151706
+ };
151707
+ };
151708
+ };
151709
+ };
151710
+ put?: never;
151711
+ post?: never;
151712
+ delete?: never;
151713
+ options?: never;
151714
+ head?: never;
151715
+ patch?: never;
151716
+ trace?: never;
151717
+ };
151718
+ "/ads/leadgen-forms": {
151719
+ parameters: {
151720
+ query?: never;
151721
+ header?: never;
151722
+ path?: never;
151723
+ cookie?: never;
151724
+ };
151725
+ /**
151726
+ * Instant Form mappings
151727
+ * @description The Meta Instant Forms this workspace mapped, and the built-in presets. A form with no entry uses the default mapping (Meta’s standard fields — name, phone, email, company — and every other answer kept in the lead note) and the inbox intake. Submissions arrive from Atribu once Meta approves lead access for the app.
151728
+ */
151729
+ get: {
151730
+ parameters: {
151731
+ query?: never;
151732
+ header?: never;
151733
+ path?: never;
151734
+ cookie?: never;
151735
+ };
151736
+ requestBody?: never;
151737
+ responses: {
151738
+ /** @description The forms and presets. */
151739
+ 200: {
151740
+ headers: {
151741
+ [name: string]: unknown;
151742
+ };
151743
+ content: {
151744
+ /**
151745
+ * @example {
151746
+ * "data": {
151747
+ * "forms": [
151748
+ * {
151749
+ * "form_id": "1234567890123456",
151750
+ * "name": "Vitrina · Automotoras · Higher intent v1",
151751
+ * "intake": "demo",
151752
+ * "enabled": true,
151753
+ * "mapping": {
151754
+ * "vertical": "automotora",
151755
+ * "rules": []
151756
+ * },
151757
+ * "updated_at": "2026-10-11T15:00:00.000Z"
151758
+ * }
151759
+ * ],
151760
+ * "presets": [
151761
+ * "vitrina_automotora_v1"
151762
+ * ]
151763
+ * }
151764
+ * }
151765
+ */
151766
+ "application/json": {
151767
+ data: {
151768
+ forms: {
151769
+ form_id: string;
151770
+ name: string | null;
151771
+ /** @enum {string} */
151772
+ intake: "inbox" | "demo";
151773
+ enabled: boolean;
151774
+ mapping: {
151775
+ [key: string]: unknown;
151776
+ };
151777
+ updated_at: string;
151778
+ }[];
151779
+ presets: string[];
151780
+ };
151781
+ };
151782
+ };
151783
+ };
151784
+ /** @description Validation error */
151785
+ 400: {
151786
+ headers: {
151787
+ [name: string]: unknown;
151788
+ };
151789
+ content: {
151790
+ "application/json": components["schemas"]["Error"];
151791
+ };
151792
+ };
151793
+ /** @description Unauthorized */
151794
+ 401: {
151795
+ headers: {
151796
+ [name: string]: unknown;
151797
+ };
151798
+ content: {
151799
+ "application/json": components["schemas"]["Error"];
151800
+ };
151801
+ };
151802
+ /** @description Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. */
151803
+ 402: {
151804
+ headers: {
151805
+ [name: string]: unknown;
151806
+ };
151807
+ content: {
151808
+ /**
151809
+ * @example {
151810
+ * "error": {
151811
+ * "code": "ENTITLEMENT_NOT_ACTIVE",
151812
+ * "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
151813
+ * "details": {
151814
+ * "required": [
151815
+ * "vitrina_ads"
151816
+ * ],
151817
+ * "active": [],
151818
+ * "feature": "vitrina_ads",
151819
+ * "entitlement_state": "off"
151820
+ * }
151821
+ * }
151822
+ * }
151823
+ */
151824
+ "application/json": components["schemas"]["Error"];
151825
+ };
151826
+ };
151827
+ /** @description Forbidden */
151828
+ 403: {
151829
+ headers: {
151830
+ [name: string]: unknown;
151831
+ };
151832
+ content: {
151833
+ "application/json": components["schemas"]["Error"];
151834
+ };
151835
+ };
151836
+ /** @description Not found */
151837
+ 404: {
151838
+ headers: {
151839
+ [name: string]: unknown;
151840
+ };
151841
+ content: {
151842
+ "application/json": components["schemas"]["Error"];
151843
+ };
151844
+ };
151845
+ /** @description Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write. */
151846
+ 409: {
151847
+ headers: {
151848
+ [name: string]: unknown;
151849
+ };
151850
+ content: {
151851
+ /**
151852
+ * @example {
151853
+ * "error": {
151854
+ * "code": "ADS_KEY_NEEDS_REMINT",
151855
+ * "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
151856
+ * "details": {
151857
+ * "key_kind": "core"
151858
+ * }
151859
+ * }
151860
+ * }
151861
+ */
151862
+ "application/json": components["schemas"]["Error"];
151863
+ };
151864
+ };
151865
+ /** @description Rate limited */
151866
+ 429: {
151867
+ headers: {
151868
+ [name: string]: unknown;
151869
+ };
151870
+ content: {
151871
+ "application/json": components["schemas"]["Error"];
151872
+ };
151873
+ };
151874
+ };
151875
+ };
151876
+ put?: never;
151877
+ post?: never;
151878
+ delete?: never;
151879
+ options?: never;
151880
+ head?: never;
151881
+ patch?: never;
151882
+ trace?: never;
151883
+ };
151884
+ "/ads/leadgen-forms/{form_id}": {
151885
+ parameters: {
151886
+ query?: never;
151887
+ header?: never;
151888
+ path?: never;
151889
+ cookie?: never;
151890
+ };
151891
+ get?: never;
151892
+ /**
151893
+ * Map an Instant Form
151894
+ * @description Creates or replaces the mapping of one Instant Form: which question is the name, phone, email and company, how fit answers are read, which checkbox is the marketing opt-in and whether contacting the person is allowed by the submission itself (`contact_consent`). `intake: demo` feeds the founders’ demo intake (409 `no_demo_booking` for a workspace that takes none). A missing answer never blocks the lead.
151895
+ */
151896
+ put: {
151897
+ parameters: {
151898
+ query?: never;
151899
+ header?: never;
151900
+ path: {
151901
+ /** @description The Instant Form's id in Meta (`form_id` of the lead). */
151902
+ form_id: string;
151903
+ };
151904
+ cookie?: never;
151905
+ };
151906
+ requestBody?: {
151907
+ content: {
151908
+ /**
151909
+ * @example {
151910
+ * "name": "Vitrina · Automotoras · Higher intent v1",
151911
+ * "intake": "demo",
151912
+ * "preset": "vitrina_automotora_v1"
151913
+ * }
151914
+ */
151915
+ "application/json": {
151916
+ name?: string;
151917
+ /**
151918
+ * @description `inbox`: a contact, a lead and an inbox conversation. `demo`: the form feeds the founders’ demo intake (only workspaces that take demo bookings): fit score, Lead / QualifiedLead events and the 7-day follow-up.
151919
+ * @default inbox
151920
+ * @enum {string}
151921
+ */
151922
+ intake?: "inbox" | "demo";
151923
+ /**
151924
+ * @description A built-in mapping (`vitrina_automotora_v1`: the Vitrina «Automotoras» form). Replaces `mapping`.
151925
+ * @enum {string}
151926
+ */
151927
+ preset?: "vitrina_automotora_v1";
151928
+ /** @description Per-question rules. A rule names the Meta question (`source`, compared lowercase / without accents / with `_` for spaces; `aliases` add more spellings), where its answer goes (`target`: contact.name, contact.first_name, contact.last_name, contact.phone, contact.email, contact.company, qualification.role, qualification.size, qualification.sellers, note), and optionally `values` (normalised answer → stored value). Every answer is kept in the lead note whatever its rule; a missing answer never blocks the lead. */
151929
+ mapping?: {
151930
+ /** @default [] */
151931
+ rules?: {
151932
+ source: string;
151933
+ aliases?: string[];
151934
+ /** @enum {string} */
151935
+ target: "contact.name" | "contact.first_name" | "contact.last_name" | "contact.phone" | "contact.email" | "contact.company" | "qualification.role" | "qualification.size" | "qualification.sellers" | "note";
151936
+ label?: string;
151937
+ values?: {
151938
+ [key: string]: string;
151939
+ };
151940
+ }[];
151941
+ marketing_checkbox_key?: string;
151942
+ notice_version?: string;
151943
+ marketing_text_version?: string;
151944
+ /** @enum {string} */
151945
+ contact_consent?: "none" | "submission" | "disclaimer";
151946
+ /** @enum {string} */
151947
+ vertical?: "automotora" | "clinica" | "otro";
151948
+ };
151949
+ /** @default true */
151950
+ enabled?: boolean;
151951
+ };
151952
+ };
151953
+ };
151954
+ responses: {
151955
+ /** @description The stored mapping. */
151956
+ 200: {
151957
+ headers: {
151958
+ [name: string]: unknown;
151959
+ };
151960
+ content: {
151961
+ /**
151962
+ * @example {
151963
+ * "data": {
151964
+ * "form_id": "1234567890123456",
151965
+ * "name": "Vitrina · Automotoras · Higher intent v1",
151966
+ * "intake": "demo",
151967
+ * "enabled": true,
151968
+ * "mapping": {
151969
+ * "vertical": "automotora",
151970
+ * "rules": []
151971
+ * },
151972
+ * "updated_at": "2026-10-11T15:00:00.000Z"
151973
+ * }
151974
+ * }
151975
+ */
151976
+ "application/json": {
151977
+ data: {
151978
+ form_id: string;
151979
+ name: string | null;
151980
+ /** @enum {string} */
151981
+ intake: "inbox" | "demo";
151982
+ enabled: boolean;
151983
+ mapping: {
151984
+ [key: string]: unknown;
151985
+ };
151986
+ updated_at: string;
151987
+ };
151988
+ };
151989
+ };
151990
+ };
151991
+ /** @description Validation error */
151992
+ 400: {
151993
+ headers: {
151994
+ [name: string]: unknown;
151995
+ };
151996
+ content: {
151997
+ "application/json": components["schemas"]["Error"];
151998
+ };
151999
+ };
152000
+ /** @description Unauthorized */
152001
+ 401: {
152002
+ headers: {
152003
+ [name: string]: unknown;
152004
+ };
152005
+ content: {
152006
+ "application/json": components["schemas"]["Error"];
152007
+ };
152008
+ };
152009
+ /** @description Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. */
152010
+ 402: {
152011
+ headers: {
152012
+ [name: string]: unknown;
152013
+ };
152014
+ content: {
152015
+ /**
152016
+ * @example {
152017
+ * "error": {
152018
+ * "code": "ENTITLEMENT_NOT_ACTIVE",
152019
+ * "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
152020
+ * "details": {
152021
+ * "required": [
152022
+ * "vitrina_ads"
152023
+ * ],
152024
+ * "active": [],
152025
+ * "feature": "vitrina_ads",
152026
+ * "entitlement_state": "off"
152027
+ * }
152028
+ * }
152029
+ * }
152030
+ */
152031
+ "application/json": components["schemas"]["Error"];
152032
+ };
152033
+ };
152034
+ /** @description Forbidden */
152035
+ 403: {
152036
+ headers: {
152037
+ [name: string]: unknown;
152038
+ };
152039
+ content: {
152040
+ "application/json": components["schemas"]["Error"];
152041
+ };
152042
+ };
152043
+ /** @description Not found */
152044
+ 404: {
152045
+ headers: {
152046
+ [name: string]: unknown;
152047
+ };
152048
+ content: {
152049
+ "application/json": components["schemas"]["Error"];
152050
+ };
152051
+ };
152052
+ /** @description Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write. */
152053
+ 409: {
152054
+ headers: {
152055
+ [name: string]: unknown;
152056
+ };
152057
+ content: {
152058
+ /**
152059
+ * @example {
152060
+ * "error": {
152061
+ * "code": "ADS_KEY_NEEDS_REMINT",
152062
+ * "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
152063
+ * "details": {
152064
+ * "key_kind": "core"
152065
+ * }
152066
+ * }
152067
+ * }
152068
+ */
152069
+ "application/json": components["schemas"]["Error"];
152070
+ };
152071
+ };
152072
+ /** @description Rate limited */
152073
+ 429: {
152074
+ headers: {
152075
+ [name: string]: unknown;
152076
+ };
152077
+ content: {
152078
+ "application/json": components["schemas"]["Error"];
152079
+ };
152080
+ };
152081
+ };
152082
+ };
150137
152083
  post?: never;
150138
- delete?: never;
150139
- options?: never;
150140
- head?: never;
150141
152084
  /**
150142
- * Record an arm’s ad sets, start, end or read early
150143
- * @description `arm` records the ad sets (and campaign) an arm runs on, as built in Ads Manager — their learning read restarts. `status: running` once the test arm is live (reads learning right away); `ended` closes it and keeps the record. `read_from` compares from a day before every arm left learning (the readout then says `read_early: true`).
152085
+ * Remove an Instant Form mapping
152086
+ * @description Back to the default mapping and the inbox intake for that form. Leads already created are untouched.
150144
152087
  */
150145
- patch: {
152088
+ delete: {
150146
152089
  parameters: {
150147
152090
  query?: never;
150148
152091
  header?: never;
150149
152092
  path: {
150150
- id: string;
152093
+ /** @description The Instant Form's id in Meta (`form_id` of the lead). */
152094
+ form_id: string;
150151
152095
  };
150152
152096
  cookie?: never;
150153
152097
  };
150154
- requestBody?: {
150155
- content: {
150156
- /**
150157
- * @example {
150158
- * "arm": {
150159
- * "arm_key": "first_payment",
150160
- * "ad_sets": [
150161
- * "120211000000000301"
150162
- * ],
150163
- * "campaign": "120211000000000300"
150164
- * },
150165
- * "status": "running"
150166
- * }
150167
- */
150168
- "application/json": {
150169
- arm?: {
150170
- arm_key: string;
150171
- ad_sets?: string[];
150172
- campaign?: string | null;
150173
- budget_share?: number | null;
150174
- };
150175
- /**
150176
- * @description `running` once the test arm is live in Meta (needs its ad sets); `ended` closes it (the record is kept).
150177
- * @enum {string}
150178
- */
150179
- status?: "running" | "ended";
150180
- /** @description Compare from this clinic day even if an arm is still learning (the readout then says `read_early: true`); `null` = wait for learning. */
150181
- read_from?: string | null;
150182
- };
150183
- };
150184
- };
152098
+ requestBody?: never;
150185
152099
  responses: {
150186
- /** @description The experiment after the change. */
150187
- 200: {
152100
+ /** @description Removed. */
152101
+ 204: {
150188
152102
  headers: {
150189
152103
  [name: string]: unknown;
150190
152104
  };
150191
- content: {
150192
- /**
150193
- * @example {
150194
- * "data": {
150195
- * "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
150196
- * "name": "Conversaciones vs primer pago",
150197
- * "status": "running",
150198
- * "started_at": "2026-10-02T13:00:00.000Z",
150199
- * "ended_at": null,
150200
- * "readout_from": null,
150201
- * "read_early": false,
150202
- * "created_at": "2026-10-01T21:10:00.000Z",
150203
- * "arms": [
150204
- * {
150205
- * "arm_key": "control",
150206
- * "role": "control",
150207
- * "optimization_event": "conversations",
150208
- * "meta_campaign": null,
150209
- * "meta_ad_sets": [
150210
- * "120211000000000201"
150211
- * ],
150212
- * "budget_share": 50,
150213
- * "learning_status": "active",
150214
- * "learning_checked_at": "2026-10-03T12:00:00.000Z",
150215
- * "left_learning_at": "2026-10-02T18:00:00.000Z"
150216
- * },
150217
- * {
150218
- * "arm_key": "first_payment",
150219
- * "role": "test",
150220
- * "optimization_event": "first_payment",
150221
- * "meta_campaign": "120211000000000300",
150222
- * "meta_ad_sets": [
150223
- * "120211000000000301"
150224
- * ],
150225
- * "budget_share": 50,
150226
- * "learning_status": "learning",
150227
- * "learning_checked_at": "2026-10-03T12:00:00.000Z",
150228
- * "left_learning_at": null
150229
- * }
150230
- * ]
150231
- * }
150232
- * }
150233
- */
150234
- "application/json": {
150235
- data: components["schemas"]["AdsExperiment"];
150236
- };
150237
- };
152105
+ content?: never;
150238
152106
  };
150239
152107
  /** @description Validation error */
150240
152108
  400: {
@@ -150328,9 +152196,12 @@ interface paths {
150328
152196
  };
150329
152197
  };
150330
152198
  };
152199
+ options?: never;
152200
+ head?: never;
152201
+ patch?: never;
150331
152202
  trace?: never;
150332
152203
  };
150333
- "/ads/experiments/{id}/readout": {
152204
+ "/ads/leadgen-submissions": {
150334
152205
  parameters: {
150335
152206
  query?: never;
150336
152207
  header?: never;
@@ -150338,21 +152209,21 @@ interface paths {
150338
152209
  cookie?: never;
150339
152210
  };
150340
152211
  /**
150341
- * The experiment readout, per arm
150342
- * @description Per arm: spend, people whose WhatsApp conversation the arm’s ads opened, «pacientes nuevos que pagaron» (a first payment), accepted presupuestos, and the two costs — cost per paying patient and cost per accepted presupuesto — side by side with the days of data. No winner and no probability. Numbers appear from the day the last arm left Meta’s learning phase (`phase: learning` until then), or from the day the owner chose to read early (`read_early: true`).
152212
+ * Latest Instant Form submissions
152213
+ * @description The latest submissions received, newest first: whether each was applied (`processed`), is being applied (`received`) or failed and will be retried (`failed`, with the error), which intake took it, and the lead and contact it produced. No answers.
150343
152214
  */
150344
152215
  get: {
150345
152216
  parameters: {
150346
- query?: never;
150347
- header?: never;
150348
- path: {
150349
- id: string;
152217
+ query?: {
152218
+ limit?: number;
150350
152219
  };
152220
+ header?: never;
152221
+ path?: never;
150351
152222
  cookie?: never;
150352
152223
  };
150353
152224
  requestBody?: never;
150354
152225
  responses: {
150355
- /** @description The readout. */
152226
+ /** @description The submissions. */
150356
152227
  200: {
150357
152228
  headers: {
150358
152229
  [name: string]: unknown;
@@ -150361,146 +152232,33 @@ interface paths {
150361
152232
  /**
150362
152233
  * @example {
150363
152234
  * "data": {
150364
- * "experiment": {
150365
- * "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
150366
- * "name": "Conversaciones vs primer pago",
150367
- * "status": "running",
150368
- * "started_at": "2026-10-02T13:00:00.000Z",
150369
- * "ended_at": null,
150370
- * "readout_from": null,
150371
- * "read_early": false,
150372
- * "created_at": "2026-10-01T21:10:00.000Z",
150373
- * "arms": [
150374
- * {
150375
- * "arm_key": "control",
150376
- * "role": "control",
150377
- * "optimization_event": "conversations",
150378
- * "meta_campaign": null,
150379
- * "meta_ad_sets": [
150380
- * "120211000000000201"
150381
- * ],
150382
- * "budget_share": 50,
150383
- * "learning_status": "active",
150384
- * "learning_checked_at": "2026-10-03T12:00:00.000Z",
150385
- * "left_learning_at": "2026-10-02T18:00:00.000Z"
150386
- * },
150387
- * {
150388
- * "arm_key": "first_payment",
150389
- * "role": "test",
150390
- * "optimization_event": "first_payment",
150391
- * "meta_campaign": "120211000000000300",
150392
- * "meta_ad_sets": [
150393
- * "120211000000000301"
150394
- * ],
150395
- * "budget_share": 50,
150396
- * "learning_status": "learning",
150397
- * "learning_checked_at": "2026-10-03T12:00:00.000Z",
150398
- * "left_learning_at": null
150399
- * }
150400
- * ]
150401
- * },
150402
- * "readout": {
150403
- * "phase": "comparing",
150404
- * "from": "2026-10-09",
150405
- * "to": "2026-10-30",
150406
- * "days_of_data": 22,
150407
- * "day_number": 29,
150408
- * "planned_days": 28,
150409
- * "learning_finished": true,
150410
- * "read_early": false,
150411
- * "planned_end_from": "2026-10-30",
150412
- * "planned_end_to": "2026-11-13",
150413
- * "arms": [
150414
- * {
150415
- * "arm_key": "control",
150416
- * "role": "control",
150417
- * "optimization_event": "conversations",
150418
- * "ad_set_count": 1,
150419
- * "learning_status": "active",
150420
- * "left_learning_at": "2026-10-02T18:00:00.000Z",
150421
- * "spend_clp": 650000,
150422
- * "people": 210,
150423
- * "paying_patients": 6,
150424
- * "accepted_quotes": 9,
150425
- * "attended": 14,
150426
- * "cost_per_paying_patient": 108333,
150427
- * "cost_per_accepted_quote": 72222,
150428
- * "sales_objective": false
150429
- * },
150430
- * {
150431
- * "arm_key": "first_payment",
150432
- * "role": "test",
150433
- * "optimization_event": "first_payment",
150434
- * "ad_set_count": 1,
150435
- * "learning_status": "learning_limited",
150436
- * "left_learning_at": "2026-10-09T12:00:00.000Z",
150437
- * "spend_clp": 640000,
150438
- * "people": 150,
150439
- * "paying_patients": 8,
150440
- * "accepted_quotes": 10,
150441
- * "attended": 13,
150442
- * "cost_per_paying_patient": 80000,
150443
- * "cost_per_accepted_quote": 64000,
150444
- * "sales_objective": true
150445
- * }
150446
- * ]
150447
- * }
152235
+ * "submissions": [
152236
+ * {
152237
+ * "leadgen_id": "444444444444",
152238
+ * "status": "processed",
152239
+ * "route": "demo",
152240
+ * "attempts": 1,
152241
+ * "lead_id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
152242
+ * "contact_id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e20",
152243
+ * "error": null
152244
+ * }
152245
+ * ]
150448
152246
  * }
150449
152247
  * }
150450
152248
  */
150451
152249
  "application/json": {
150452
152250
  data: {
150453
- experiment: components["schemas"]["AdsExperiment"];
150454
- readout: {
150455
- /**
150456
- * @description `setup` (test arm not recorded) → `learning` (an arm is still learning; no numbers) → `comparing` → `ended`.
150457
- * @enum {string}
150458
- */
150459
- phase: "setup" | "learning" | "comparing" | "ended";
150460
- from: string | null;
150461
- to: string;
150462
- days_of_data: number;
150463
- /** @description «Día N de 28»: days since the start, 1 on the start day, while the test runs; `null` before it starts and once it ended. */
150464
- day_number: number | null;
150465
- /** @description The planned length: 28. */
150466
- planned_days: number;
150467
- learning_finished: boolean;
150468
- read_early: boolean;
150469
- /** @description 4 weeks after the start. */
150470
- planned_end_from: string | null;
150471
- /** @description 6 weeks after the start. */
150472
- planned_end_to: string | null;
150473
- arms: {
150474
- arm_key: string;
150475
- /** @enum {string} */
150476
- role: "control" | "test";
150477
- /**
150478
- * @description What the arm’s ad sets optimise for: `conversations` (Meta’s own «conversaciones», the control) or a stage Vitrina sends to Meta (`first_payment` = «primer pago», `closed_won` = «presupuesto aceptado», `appointment_attended` = «cita atendida»).
150479
- * @enum {string}
150480
- */
150481
- optimization_event: "conversations" | "first_payment" | "closed_won" | "appointment_attended" | "appointment_booked" | "lead_created";
150482
- ad_set_count: number;
150483
- /**
150484
- * @description Meta’s learning stage for the arm’s ad sets, READ from Meta (never guessed): `learning` (Aprendiendo), `learning_limited` (Aprendizaje limitado — too few events to stabilise), `active` (out of learning), `unknown` until a read answers. The least advanced ad set decides.
150485
- * @enum {string}
150486
- */
150487
- learning_status: "learning" | "learning_limited" | "active" | "unknown";
150488
- left_learning_at: string | null;
150489
- /** @description Spend over the window, CLP; `null` = unknown or not started. */
150490
- spend_clp: number | null;
150491
- /** @description People whose WhatsApp conversation one of the arm’s ads opened in the window («personas»). */
150492
- people: number;
150493
- /** @description Of them, with a first payment recorded afterwards («pacientes nuevos que pagaron»). */
150494
- paying_patients: number;
150495
- accepted_quotes: number;
150496
- attended: number;
150497
- /** @description spend / paying_patients, CLP; `null` with none (never 0). */
150498
- cost_per_paying_patient: number | null;
150499
- cost_per_accepted_quote: number | null;
150500
- /** @description The arm runs under a Sales objective (required to optimise click-to-WhatsApp ads on the first payment); `null` = the engine does not report the objective. */
150501
- sales_objective: boolean | null;
150502
- }[];
150503
- };
152251
+ submissions: {
152252
+ leadgen_id: string;
152253
+ /** @enum {string} */
152254
+ status: "received" | "processed" | "failed";
152255
+ /** @enum {string|null} */
152256
+ route: "demo" | "inbox" | "inbox_fallback" | null;
152257
+ attempts: number;
152258
+ lead_id: string | null;
152259
+ contact_id: string | null;
152260
+ error: string | null;
152261
+ }[];
150504
152262
  };
150505
152263
  };
150506
152264
  };
@@ -175593,7 +177351,7 @@ interface components {
175593
177351
  };
175594
177352
  WebhookEventDoc: {
175595
177353
  /** @enum {string} */
175596
- name: "ai_agent.publish" | "ai_agent_graph.publish" | "agent_scenario_run.completed" | "agent_scenario_suite.completed" | "conversation.created" | "conversation.assigned" | "conversation.resolved" | "message.received" | "message.sent" | "ticket.opened" | "ticket.reopened" | "ticket.resolved" | "ticket.closed" | "sla.breached" | "kb.source.create" | "kb.source.embed" | "kb.source.update" | "kb.source.delete" | "kb_file.uploaded" | "kb_file.deleted" | "skill.created" | "skill.updated" | "skill.deleted" | "custom_tool.created" | "custom_tool.updated" | "custom_tool.deleted" | "macro.applied" | "cost.budget_alert" | "runtime.budget_exceeded" | "lead.created" | "lead.stage_changed" | "lead.pipeline_changed" | "lead.assigned" | "lead.won" | "lead.lost" | "lead.unqualified" | "stock.synced" | "stock.source_retired" | "vehicle.published" | "vehicle.unpublished" | "vehicle.publish_failed" | "vehicle.created" | "vehicle.attachment.created" | "mercadolibre.order" | "mercadolibre.vis_lead" | "chileautos.lead" | "yapo.lead" | "website.lead" | "demo_lead.created" | "demo_lead.pending_scheduling" | "demo_lead.routed" | "demo_booking.booked" | "demo_booking.rescheduled" | "demo_booking.cancelled" | "consignment.candidate.created" | "quote.issued" | "quote.voided" | "quote.extended" | "quote.archived" | "reservation.created" | "reservation.voided" | "reservation.abono_disposed" | "reservation.converted" | "sale_note.issued" | "sale_note.approved" | "sale_note.voided" | "purchase_note.issued" | "purchase_note.corrected" | "purchase_note.voided" | "payment.recorded" | "consignment.created" | "consignment.returned" | "consignment.sold" | "credit_application.recorded" | "credit_application.submitted" | "credit_application.decided" | "credit_application.withdrawn" | "price_approval.requested" | "price_approval.decided" | "contact.created" | "contact.updated" | "contact.merged" | "appointment.booked" | "appointment.rescheduled" | "appointment.cancelled" | "appointment.completed" | "appointment.no_show" | "appointment.reminded" | "appointment.imported" | "campaign.sent" | "campaign.paused" | "sending_domain.verified" | "coach.proposals_ready" | "coach.finding_ready" | "voice.autonomous_call" | "safety_hold.placed" | "safety_hold.ack_overdue" | "safety_hold.first_response_overdue" | "team.created" | "team.updated" | "team.deleted" | "custom_role.created" | "custom_role.updated" | "custom_role.deleted" | "macro.created" | "macro.updated" | "macro.deleted" | "assignment_rule.created" | "assignment_rule.updated" | "assignment_rule.deleted" | "sla_policy.created" | "sla_policy.updated" | "sla_policy.deleted" | "automation_trigger.created" | "automation_trigger.updated" | "automation_trigger.deleted" | "sending_domain.created" | "sending_domain.deleted" | "whatsapp_template.created" | "whatsapp_template.status_changed" | "whatsapp_template.deleted" | "whatsapp_flow.created" | "whatsapp_flow.published" | "whatsapp_flow.deprecated" | "whatsapp_flow.deleted" | "whatsapp_flow.completed" | "channel.connected" | "channel.updated" | "channel.disconnected" | "membership.created" | "membership.updated" | "membership.removed" | "clinic_professional.created" | "clinic_professional.updated" | "clinic_professional.deleted" | "clinic_service.created" | "clinic_service.updated" | "clinic_service.deleted" | "clinic_price_list.updated" | "clinic_pack_purchase.created" | "clinic_pack_purchase.session_consumed" | "clinic_pack_purchase.cancelled" | "clinic_budget.created" | "clinic_budget.sent" | "clinic_budget.accepted" | "clinic_budget.rejected" | "clinic_budget.voided" | "clinic_budget.mirror_changed" | "clinic_cash_session.opened" | "clinic_cash_session.closed" | "clinic_patient.created" | "clinic_patient.updated" | "clinic_patient.deleted" | "clinic_patient.merged" | "clinic_encounter.opened" | "clinic_encounter.closed" | "clinic_note.signed" | "clinic_note.amended" | "clinic_consent.signed" | "clinic_consent.revoked" | "clinic_document.uploaded" | "clinic_document.deleted" | "clinic_privacy_request.completed";
177354
+ name: "ai_agent.publish" | "ai_agent_graph.publish" | "agent_scenario_run.completed" | "agent_scenario_suite.completed" | "conversation.created" | "conversation.assigned" | "conversation.resolved" | "message.received" | "message.sent" | "ticket.opened" | "ticket.reopened" | "ticket.resolved" | "ticket.closed" | "sla.breached" | "kb.source.create" | "kb.source.embed" | "kb.source.update" | "kb.source.delete" | "kb_file.uploaded" | "kb_file.deleted" | "skill.created" | "skill.updated" | "skill.deleted" | "custom_tool.created" | "custom_tool.updated" | "custom_tool.deleted" | "macro.applied" | "cost.budget_alert" | "runtime.budget_exceeded" | "lead.created" | "lead.stage_changed" | "lead.pipeline_changed" | "lead.assigned" | "lead.won" | "lead.lost" | "lead.unqualified" | "stock.synced" | "stock.source_retired" | "vehicle.published" | "vehicle.unpublished" | "vehicle.publish_failed" | "vehicle.created" | "vehicle.attachment.created" | "mercadolibre.order" | "mercadolibre.vis_lead" | "chileautos.lead" | "yapo.lead" | "website.lead" | "demo_lead.created" | "demo_lead.pending_scheduling" | "demo_lead.routed" | "demo_booking.booked" | "demo_booking.rescheduled" | "demo_booking.cancelled" | "demo_booking.held" | "demo_booking.no_show" | "consignment.candidate.created" | "quote.issued" | "quote.voided" | "quote.extended" | "quote.archived" | "reservation.created" | "reservation.voided" | "reservation.abono_disposed" | "reservation.converted" | "sale_note.issued" | "sale_note.approved" | "sale_note.voided" | "purchase_note.issued" | "purchase_note.corrected" | "purchase_note.voided" | "payment.recorded" | "consignment.created" | "consignment.returned" | "consignment.sold" | "credit_application.recorded" | "credit_application.submitted" | "credit_application.decided" | "credit_application.withdrawn" | "price_approval.requested" | "price_approval.decided" | "contact.created" | "contact.updated" | "contact.merged" | "appointment.booked" | "appointment.rescheduled" | "appointment.cancelled" | "appointment.completed" | "appointment.no_show" | "appointment.reminded" | "appointment.imported" | "campaign.sent" | "campaign.paused" | "sending_domain.verified" | "coach.proposals_ready" | "coach.finding_ready" | "voice.autonomous_call" | "safety_hold.placed" | "safety_hold.ack_overdue" | "safety_hold.first_response_overdue" | "team.created" | "team.updated" | "team.deleted" | "custom_role.created" | "custom_role.updated" | "custom_role.deleted" | "macro.created" | "macro.updated" | "macro.deleted" | "assignment_rule.created" | "assignment_rule.updated" | "assignment_rule.deleted" | "sla_policy.created" | "sla_policy.updated" | "sla_policy.deleted" | "automation_trigger.created" | "automation_trigger.updated" | "automation_trigger.deleted" | "sending_domain.created" | "sending_domain.deleted" | "whatsapp_template.created" | "whatsapp_template.status_changed" | "whatsapp_template.deleted" | "whatsapp_flow.created" | "whatsapp_flow.published" | "whatsapp_flow.deprecated" | "whatsapp_flow.deleted" | "whatsapp_flow.completed" | "channel.connected" | "channel.updated" | "channel.disconnected" | "membership.created" | "membership.updated" | "membership.removed" | "clinic_professional.created" | "clinic_professional.updated" | "clinic_professional.deleted" | "clinic_service.created" | "clinic_service.updated" | "clinic_service.deleted" | "clinic_price_list.updated" | "clinic_pack_purchase.created" | "clinic_pack_purchase.session_consumed" | "clinic_pack_purchase.cancelled" | "clinic_budget.created" | "clinic_budget.sent" | "clinic_budget.accepted" | "clinic_budget.rejected" | "clinic_budget.voided" | "clinic_budget.mirror_changed" | "clinic_cash_session.opened" | "clinic_cash_session.closed" | "clinic_patient.created" | "clinic_patient.updated" | "clinic_patient.deleted" | "clinic_patient.merged" | "clinic_encounter.opened" | "clinic_encounter.closed" | "clinic_note.signed" | "clinic_note.amended" | "clinic_consent.signed" | "clinic_consent.revoked" | "clinic_document.uploaded" | "clinic_document.deleted" | "clinic_privacy_request.completed";
175597
177355
  /** @description The schema version of this event type — every delivery carries it as `version`. Bumped only on a breaking change to `data`. */
175598
177356
  version: number;
175599
177357
  /** @description What the event is about — the `resource.type` of every delivery (`lead`, `vehicle`, `contact`, …). */
@@ -175660,7 +177418,7 @@ interface components {
175660
177418
  CreateWebhookBody: {
175661
177419
  /** Format: uri */
175662
177420
  url: string;
175663
- events: ("*" | "ai_agent.publish" | "ai_agent_graph.publish" | "agent_scenario_run.completed" | "agent_scenario_suite.completed" | "conversation.created" | "conversation.assigned" | "conversation.resolved" | "message.received" | "message.sent" | "ticket.opened" | "ticket.reopened" | "ticket.resolved" | "ticket.closed" | "sla.breached" | "kb.source.create" | "kb.source.embed" | "kb.source.update" | "kb.source.delete" | "kb_file.uploaded" | "kb_file.deleted" | "skill.created" | "skill.updated" | "skill.deleted" | "custom_tool.created" | "custom_tool.updated" | "custom_tool.deleted" | "macro.applied" | "cost.budget_alert" | "runtime.budget_exceeded" | "lead.created" | "lead.stage_changed" | "lead.pipeline_changed" | "lead.assigned" | "lead.won" | "lead.lost" | "lead.unqualified" | "stock.synced" | "stock.source_retired" | "vehicle.published" | "vehicle.unpublished" | "vehicle.publish_failed" | "vehicle.created" | "vehicle.attachment.created" | "mercadolibre.order" | "mercadolibre.vis_lead" | "chileautos.lead" | "yapo.lead" | "website.lead" | "demo_lead.created" | "demo_lead.pending_scheduling" | "demo_lead.routed" | "demo_booking.booked" | "demo_booking.rescheduled" | "demo_booking.cancelled" | "consignment.candidate.created" | "quote.issued" | "quote.voided" | "quote.extended" | "quote.archived" | "reservation.created" | "reservation.voided" | "reservation.abono_disposed" | "reservation.converted" | "sale_note.issued" | "sale_note.approved" | "sale_note.voided" | "purchase_note.issued" | "purchase_note.corrected" | "purchase_note.voided" | "payment.recorded" | "consignment.created" | "consignment.returned" | "consignment.sold" | "credit_application.recorded" | "credit_application.submitted" | "credit_application.decided" | "credit_application.withdrawn" | "price_approval.requested" | "price_approval.decided" | "contact.created" | "contact.updated" | "contact.merged" | "appointment.booked" | "appointment.rescheduled" | "appointment.cancelled" | "appointment.completed" | "appointment.no_show" | "appointment.reminded" | "appointment.imported" | "campaign.sent" | "campaign.paused" | "sending_domain.verified" | "coach.proposals_ready" | "coach.finding_ready" | "voice.autonomous_call" | "safety_hold.placed" | "safety_hold.ack_overdue" | "safety_hold.first_response_overdue" | "team.created" | "team.updated" | "team.deleted" | "custom_role.created" | "custom_role.updated" | "custom_role.deleted" | "macro.created" | "macro.updated" | "macro.deleted" | "assignment_rule.created" | "assignment_rule.updated" | "assignment_rule.deleted" | "sla_policy.created" | "sla_policy.updated" | "sla_policy.deleted" | "automation_trigger.created" | "automation_trigger.updated" | "automation_trigger.deleted" | "sending_domain.created" | "sending_domain.deleted" | "whatsapp_template.created" | "whatsapp_template.status_changed" | "whatsapp_template.deleted" | "whatsapp_flow.created" | "whatsapp_flow.published" | "whatsapp_flow.deprecated" | "whatsapp_flow.deleted" | "whatsapp_flow.completed" | "channel.connected" | "channel.updated" | "channel.disconnected" | "membership.created" | "membership.updated" | "membership.removed" | "clinic_professional.created" | "clinic_professional.updated" | "clinic_professional.deleted" | "clinic_service.created" | "clinic_service.updated" | "clinic_service.deleted" | "clinic_price_list.updated" | "clinic_pack_purchase.created" | "clinic_pack_purchase.session_consumed" | "clinic_pack_purchase.cancelled" | "clinic_budget.created" | "clinic_budget.sent" | "clinic_budget.accepted" | "clinic_budget.rejected" | "clinic_budget.voided" | "clinic_budget.mirror_changed" | "clinic_cash_session.opened" | "clinic_cash_session.closed" | "clinic_patient.created" | "clinic_patient.updated" | "clinic_patient.deleted" | "clinic_patient.merged" | "clinic_encounter.opened" | "clinic_encounter.closed" | "clinic_note.signed" | "clinic_note.amended" | "clinic_consent.signed" | "clinic_consent.revoked" | "clinic_document.uploaded" | "clinic_document.deleted" | "clinic_privacy_request.completed")[];
177421
+ events: ("*" | "ai_agent.publish" | "ai_agent_graph.publish" | "agent_scenario_run.completed" | "agent_scenario_suite.completed" | "conversation.created" | "conversation.assigned" | "conversation.resolved" | "message.received" | "message.sent" | "ticket.opened" | "ticket.reopened" | "ticket.resolved" | "ticket.closed" | "sla.breached" | "kb.source.create" | "kb.source.embed" | "kb.source.update" | "kb.source.delete" | "kb_file.uploaded" | "kb_file.deleted" | "skill.created" | "skill.updated" | "skill.deleted" | "custom_tool.created" | "custom_tool.updated" | "custom_tool.deleted" | "macro.applied" | "cost.budget_alert" | "runtime.budget_exceeded" | "lead.created" | "lead.stage_changed" | "lead.pipeline_changed" | "lead.assigned" | "lead.won" | "lead.lost" | "lead.unqualified" | "stock.synced" | "stock.source_retired" | "vehicle.published" | "vehicle.unpublished" | "vehicle.publish_failed" | "vehicle.created" | "vehicle.attachment.created" | "mercadolibre.order" | "mercadolibre.vis_lead" | "chileautos.lead" | "yapo.lead" | "website.lead" | "demo_lead.created" | "demo_lead.pending_scheduling" | "demo_lead.routed" | "demo_booking.booked" | "demo_booking.rescheduled" | "demo_booking.cancelled" | "demo_booking.held" | "demo_booking.no_show" | "consignment.candidate.created" | "quote.issued" | "quote.voided" | "quote.extended" | "quote.archived" | "reservation.created" | "reservation.voided" | "reservation.abono_disposed" | "reservation.converted" | "sale_note.issued" | "sale_note.approved" | "sale_note.voided" | "purchase_note.issued" | "purchase_note.corrected" | "purchase_note.voided" | "payment.recorded" | "consignment.created" | "consignment.returned" | "consignment.sold" | "credit_application.recorded" | "credit_application.submitted" | "credit_application.decided" | "credit_application.withdrawn" | "price_approval.requested" | "price_approval.decided" | "contact.created" | "contact.updated" | "contact.merged" | "appointment.booked" | "appointment.rescheduled" | "appointment.cancelled" | "appointment.completed" | "appointment.no_show" | "appointment.reminded" | "appointment.imported" | "campaign.sent" | "campaign.paused" | "sending_domain.verified" | "coach.proposals_ready" | "coach.finding_ready" | "voice.autonomous_call" | "safety_hold.placed" | "safety_hold.ack_overdue" | "safety_hold.first_response_overdue" | "team.created" | "team.updated" | "team.deleted" | "custom_role.created" | "custom_role.updated" | "custom_role.deleted" | "macro.created" | "macro.updated" | "macro.deleted" | "assignment_rule.created" | "assignment_rule.updated" | "assignment_rule.deleted" | "sla_policy.created" | "sla_policy.updated" | "sla_policy.deleted" | "automation_trigger.created" | "automation_trigger.updated" | "automation_trigger.deleted" | "sending_domain.created" | "sending_domain.deleted" | "whatsapp_template.created" | "whatsapp_template.status_changed" | "whatsapp_template.deleted" | "whatsapp_flow.created" | "whatsapp_flow.published" | "whatsapp_flow.deprecated" | "whatsapp_flow.deleted" | "whatsapp_flow.completed" | "channel.connected" | "channel.updated" | "channel.disconnected" | "membership.created" | "membership.updated" | "membership.removed" | "clinic_professional.created" | "clinic_professional.updated" | "clinic_professional.deleted" | "clinic_service.created" | "clinic_service.updated" | "clinic_service.deleted" | "clinic_price_list.updated" | "clinic_pack_purchase.created" | "clinic_pack_purchase.session_consumed" | "clinic_pack_purchase.cancelled" | "clinic_budget.created" | "clinic_budget.sent" | "clinic_budget.accepted" | "clinic_budget.rejected" | "clinic_budget.voided" | "clinic_budget.mirror_changed" | "clinic_cash_session.opened" | "clinic_cash_session.closed" | "clinic_patient.created" | "clinic_patient.updated" | "clinic_patient.deleted" | "clinic_patient.merged" | "clinic_encounter.opened" | "clinic_encounter.closed" | "clinic_note.signed" | "clinic_note.amended" | "clinic_consent.signed" | "clinic_consent.revoked" | "clinic_document.uploaded" | "clinic_document.deleted" | "clinic_privacy_request.completed")[];
175664
177422
  description?: string | null;
175665
177423
  /** @description «Incluir datos del recurso». `false` (the default) = every delivery is the notice: resource, changes, author, time and `url`, with `data_omitted: "not_requested"`. `true` = each delivery also carries `data` when, at dispatch time, this subscription's OWNER — whoever created it or last changed its url, events or this flag — holds the resource read scope (`read_scope` in the catalogue) and sees every record; otherwise `data_omitted` says why. An event about a dato sensible is always the notice. */
175666
177424
  include_data?: boolean;
@@ -178690,6 +180448,8 @@ interface components {
178690
180448
  accepted_value?: number | null;
178691
180449
  /** @description How many accepted presupuestos / closed sales `accepted_value` is. Totals and campaign rows: the engine-credited list Resumen sums. */
178692
180450
  accepted_count?: number | null;
180451
+ /** @description D60 · «Con diagnóstico»: people whose conversation the entity opened and who got a presupuesto presented. Absent = unknown. */
180452
+ diagnosed?: number | null;
178693
180453
  /** @description `accepted_value` ÷ spend («Retorno en presupuestos»). `null` = unknown or no spend. */
178694
180454
  accepted_return?: number | null;
178695
180455
  patient_return?: components["schemas"]["AdsPatientReturn"];
@@ -178757,6 +180517,8 @@ interface components {
178757
180517
  accepted_value?: number | null;
178758
180518
  /** @description How many accepted presupuestos / closed sales `accepted_value` is. Totals and campaign rows: the engine-credited list Resumen sums. */
178759
180519
  accepted_count?: number | null;
180520
+ /** @description D60 · «Con diagnóstico»: people whose conversation the entity opened and who got a presupuesto presented. Absent = unknown. */
180521
+ diagnosed?: number | null;
178760
180522
  /** @description `accepted_value` ÷ spend («Retorno en presupuestos»). `null` = unknown or no spend. */
178761
180523
  accepted_return?: number | null;
178762
180524
  patient_return?: components["schemas"]["AdsPatientReturn"];
@@ -178839,6 +180601,8 @@ interface components {
178839
180601
  accepted_value?: number | null;
178840
180602
  /** @description How many accepted presupuestos / closed sales `accepted_value` is. Totals and campaign rows: the engine-credited list Resumen sums. */
178841
180603
  accepted_count?: number | null;
180604
+ /** @description D60 · «Con diagnóstico»: people whose conversation the entity opened and who got a presupuesto presented. Absent = unknown. */
180605
+ diagnosed?: number | null;
178842
180606
  /** @description `accepted_value` ÷ spend («Retorno en presupuestos»). `null` = unknown or no spend. */
178843
180607
  accepted_return?: number | null;
178844
180608
  patient_return?: components["schemas"]["AdsPatientReturn"];
@@ -178878,6 +180642,124 @@ interface components {
178878
180642
  /** @description The public Facebook watch page: the fallback link. */
178879
180643
  watch_url: string | null;
178880
180644
  };
180645
+ AdsDemoFunnelRow: {
180646
+ /** @enum {string} */
180647
+ arm: "whatsapp" | "instant_form" | "website" | "other" | "unassigned";
180648
+ /** @description Meta id at this level; `null` on an arm row and on the «sin atribución» row. */
180649
+ external_id: string | null;
180650
+ name: string | null;
180651
+ parent: {
180652
+ external_id: string | null;
180653
+ name: string | null;
180654
+ } | null;
180655
+ /** @description The arm’s people with no id at this level. */
180656
+ unattributed: boolean;
180657
+ /** @description People with a lead (`lead_created`). */
180658
+ leads: number;
180659
+ /** @description Of those, ICP-qualified (`lead_qualified`). */
180660
+ qualified: number;
180661
+ /** @description People with a demo booked (`appointment_booked`). */
180662
+ booked: number;
180663
+ /** @description People whose demo was held (`appointment_attended`). */
180664
+ held: number;
180665
+ /** @description People with a first paid invoice (`first_payment`). */
180666
+ paid: number;
180667
+ /** @description Leads that got a reply (agent, human or API) within 24 h of the lead. */
180668
+ contacted_24h: number;
180669
+ /** @description `null` = unknown or none on this row. */
180670
+ spend: number | null;
180671
+ costs: {
180672
+ per_lead: number | null;
180673
+ per_qualified: number | null;
180674
+ per_booked: number | null;
180675
+ /** @description THE metric the A/B is judged on (D5). */
180676
+ per_held: number | null;
180677
+ per_paid: number | null;
180678
+ };
180679
+ rates: {
180680
+ /** @description qualified ÷ leads */
180681
+ qualified_share: number | null;
180682
+ /** @description contacted within 24 h ÷ leads */
180683
+ contacted_24h_rate: number | null;
180684
+ /** @description booked ÷ leads */
180685
+ lead_to_booked: number | null;
180686
+ /** @description held ÷ booked */
180687
+ show_rate: number | null;
180688
+ };
180689
+ };
180690
+ AdsDemoFunnel: {
180691
+ /** @enum {string} */
180692
+ level: "arm" | "campaign" | "ad_set" | "ad";
180693
+ /** @enum {string} */
180694
+ basis: "cohort" | "event";
180695
+ window: {
180696
+ from: string;
180697
+ to: string;
180698
+ };
180699
+ rows: components["schemas"]["AdsDemoFunnelRow"][];
180700
+ arms: components["schemas"]["AdsDemoFunnelRow"][];
180701
+ totals: {
180702
+ /** @description People with a lead (`lead_created`). */
180703
+ leads: number;
180704
+ /** @description Of those, ICP-qualified (`lead_qualified`). */
180705
+ qualified: number;
180706
+ /** @description People with a demo booked (`appointment_booked`). */
180707
+ booked: number;
180708
+ /** @description People whose demo was held (`appointment_attended`). */
180709
+ held: number;
180710
+ /** @description People with a first paid invoice (`first_payment`). */
180711
+ paid: number;
180712
+ /** @description Leads that got a reply (agent, human or API) within 24 h of the lead. */
180713
+ contacted_24h: number;
180714
+ spend: number | null;
180715
+ costs: {
180716
+ per_lead: number | null;
180717
+ per_qualified: number | null;
180718
+ per_booked: number | null;
180719
+ /** @description THE metric the A/B is judged on (D5). */
180720
+ per_held: number | null;
180721
+ per_paid: number | null;
180722
+ };
180723
+ rates: {
180724
+ /** @description qualified ÷ leads */
180725
+ qualified_share: number | null;
180726
+ /** @description contacted within 24 h ÷ leads */
180727
+ contacted_24h_rate: number | null;
180728
+ /** @description booked ÷ leads */
180729
+ lead_to_booked: number | null;
180730
+ /** @description held ÷ booked */
180731
+ show_rate: number | null;
180732
+ };
180733
+ };
180734
+ reconciliation: {
180735
+ overview_spend: number | null;
180736
+ spend_matches: boolean | null;
180737
+ events: {
180738
+ lead_created: {
180739
+ events: number;
180740
+ people: number;
180741
+ };
180742
+ lead_qualified: {
180743
+ events: number;
180744
+ people: number;
180745
+ };
180746
+ appointment_booked: {
180747
+ events: number;
180748
+ people: number;
180749
+ };
180750
+ appointment_attended: {
180751
+ events: number;
180752
+ people: number;
180753
+ };
180754
+ first_payment: {
180755
+ events: number;
180756
+ people: number;
180757
+ };
180758
+ } | null;
180759
+ counts_match: boolean | null;
180760
+ leads_without_ad: number;
180761
+ };
180762
+ };
178881
180763
  /** @description «Retorno total de pacientes de anuncios (primer pago + cuotas)» over the money-covered window. On `current` only; `null` = unknown (the credit is still warming). */
178882
180764
  AdsPatientReturnTotal: components["schemas"]["AdsPatientReturn"] & ({
178883
180765
  /** @description Median days from the ad touch that counts it to each person’s first payment, over the first payments of the period. */
@@ -180440,7 +182322,7 @@ interface components {
180440
182322
  campaign_name: string | null;
180441
182323
  }[];
180442
182324
  };
180443
- /** @description The engine’s own vocabulary, e.g. `{ budget_change_pct: 20 }`. `url_tags_fix`: `{ ads: [{ external_id, expected_url_tags, url_tags }] }` — each ad’s current parameters (null = none) and exactly what it gets. «Enviar a Meta»: `send_setup`/`send_dataset` `{ dataset_id }`, `send_stage` `{ stage, enabled, meta_event, active_ad_sets }`. `campaign_pause`: `{ period_spend, period_days, delivering_ads_count, avg_daily_spend_7d }` — the campaign’s spend in the listed period (the account currency’s major units), the period’s days and its delivering ads, what the card draws at rest (spend → 0, ~spend/days a day); `avg_daily_spend_7d` is the live 7-day daily mean the preview shows (`AdsMoney`: exact decimal + currency), `null` when the list could not know it (real mode reads it only at preview) — fall back to `period_spend / period_days`, labelled as the period. `adset_pause` (D62): `{ ad_ids, period_spend, share, booking_rate, sibling_ad_set, sibling_booking_rate, budget_level }` — the ad set’s delivering ads (bound into the approval: only those the preview read as showing), its share of the campaign’s spend and its booking rate vs its best sibling’s; with a campaign budget Meta moves the money to the sibling. A `nav` card with `params.card = "campaign_budget"` is a scale on a campaign that owns its budget: `{ campaign_external_id, daily_budget, new_daily_budget, step_pct }` — the change is made in Meta (`href`). `params.card = "funnel_leak"` (D66): the account books under 10 % of the people who write from ads — `{ people, booked, booking_rate, booking_interval, benchmark, by_ad }`. Agenda capacity (clinics): a `nav` card with `params.card = "capacity_bottleneck"` REPLACES an ad's «Pausar» (`variant: "pause"`) or a «Subir» (`variant: "budget"`) when the specialty the ad books has no usable hour for more than 7 days (or under 2 bookable hours in 14): `{ card, variant, especialidad, mapping_source: "map" | "booked" | "name", unmapped: false, next_slot_day, searched_through, free_minutes_14, booked_minutes_14, fill_14, visit_minutes, evaluation_price, price_above_median, people, booked, booking_rate, median_booking_rate, waitlist_30d }` — counts, minutes and dates, never a probability; `waitlist_30d` = people waiting now for that specialty (the clinic waitlist demand feed), `null` when the feed is absent or 0 — the card then carries no «N personas esperan hora con X hoy» sentence. A `pause_ad` whose ad books an unknown specialty carries `params.capacity = { unmapped: true }` (ask which specialty; `tenant.settings.ads.specialty_map`). */
182325
+ /** @description The engine’s own vocabulary, e.g. `{ budget_change_pct: 20 }`. `url_tags_fix`: `{ ads: [{ external_id, expected_url_tags, url_tags }] }` — each ad’s current parameters (null = none) and exactly what it gets. «Enviar a Meta»: `send_setup`/`send_dataset` `{ dataset_id }`, `send_stage` `{ stage, enabled, meta_event, active_ad_sets }`. `campaign_pause`: `{ period_spend, period_days, delivering_ads_count, avg_daily_spend_7d }` — the campaign’s spend in the listed period (the account currency’s major units), the period’s days and its delivering ads, what the card draws at rest (spend → 0, ~spend/days a day); `avg_daily_spend_7d` is the live 7-day daily mean the preview shows (`AdsMoney`: exact decimal + currency), `null` when the list could not know it (real mode reads it only at preview) — fall back to `period_spend / period_days`, labelled as the period. `adset_pause` (D62): `{ ad_ids, period_spend, share, booking_rate, sibling_ad_set, sibling_booking_rate, budget_level }` — the ad set’s delivering ads (bound into the approval: only those the preview read as showing), its share of the campaign’s spend and its booking rate vs its best sibling’s; with a campaign budget Meta moves the money to the sibling. `budget_change` on a campaign (`target.level = "campaign"`, `engine_ref.op = "campaigns.budget"`, D78) raises or lowers a campaign that owns its budget (Advantage campaign budget) by `budget_change_pct` (±20 % at most, D68): `{ budget_change_pct, budget_level: "campaign", budget_type: "daily" | "lifetime", daily_budget, new_daily_budget (or lifetime_budget, new_lifetime_budget), step_pct }` — amounts in the account currency’s major units, `null` when the status read could not give them; the worker applies the step to the LIVE budget. A raise follows a winner; a lower is the D62 trim one level up (a campaign with more than its even share of the account’s spend booking under half the best other campaign’s rate). Undo = rollback. The optimisation-event cards (the duplicate card `params.optimization_event`, the experiment card, `adset_duplicate_swap`) follow D81: `appointment_booked` until the account reaches ~`first_payment_threshold` (50) first payments a week (`weekly_first_payments`), then `first_payment`. `params.card = "funnel_leak"` (D66): the account books under 10 % of the people who write from ads — `{ people, booked, booking_rate, booking_interval, benchmark, by_ad }`. Agenda capacity (clinics): a `nav` card with `params.card = "capacity_bottleneck"` REPLACES an ad's «Pausar» (`variant: "pause"`) or a «Subir» (`variant: "budget"`) when the specialty the ad books has no usable hour for more than 7 days (or under 2 bookable hours in 14): `{ card, variant, especialidad, mapping_source: "map" | "booked" | "name", unmapped: false, next_slot_day, searched_through, free_minutes_14, booked_minutes_14, fill_14, visit_minutes, evaluation_price, price_above_median, people, booked, booking_rate, median_booking_rate, waitlist_30d }` — counts, minutes and dates, never a probability; `waitlist_30d` = people waiting now for that specialty (the clinic waitlist demand feed), `null` when the feed is absent or 0 — the card then carries no «N personas esperan hora con X hoy» sentence. A `pause_ad` whose ad books an unknown specialty carries `params.capacity = { unmapped: true }` (ask which specialty; `tenant.settings.ads.specialty_map`). */
180444
182326
  params: {
180445
182327
  [key: string]: unknown;
180446
182328
  };
@@ -180450,7 +182332,7 @@ interface components {
180450
182332
  /** @description Which operation executes it; `null` for `nav`. */
180451
182333
  engine_ref: {
180452
182334
  /** @enum {string} */
180453
- op: "recommendations.apply" | "ads.pause" | "ads.pause_set" | "ads.resume" | "ads.resume_set" | "adsets.budget" | "campaigns.pause" | "campaigns.resume" | "wiring.ad_sets" | "quality.utm_refresh" | "wiring.recheck" | "actions.rollback" | "quality.url_tags_apply" | "quality.url_tags_undo" | "send.setup" | "send.dataset" | "send.stage" | "send.stop" | "send.restore";
182335
+ op: "recommendations.apply" | "ads.pause" | "ads.pause_set" | "ads.resume" | "ads.resume_set" | "adsets.budget" | "campaigns.budget" | "campaigns.pause" | "campaigns.resume" | "wiring.ad_sets" | "quality.utm_refresh" | "wiring.recheck" | "actions.rollback" | "quality.url_tags_apply" | "quality.url_tags_undo" | "send.setup" | "send.dataset" | "send.stage" | "send.stop" | "send.restore";
180454
182336
  recommendation_id?: string;
180455
182337
  } | null;
180456
182338
  /** @description `nav` only: a workspace-relative path (`/anuncios/atribuidos?c=…`, `/anuncios/activar?paso=tag`), or an absolute Meta Ads Manager URL built from Meta ids (`https://www.facebook.com/adsmanager/…`) — open it in a new tab; it changes nothing by itself. */
@@ -180541,7 +182423,7 @@ interface components {
180541
182423
  } | null;
180542
182424
  reversible: boolean;
180543
182425
  /** @enum {string|null} */
180544
- engine_op: "recommendations.apply" | "ads.pause" | "ads.pause_set" | "ads.resume" | "ads.resume_set" | "adsets.budget" | "campaigns.pause" | "campaigns.resume" | "wiring.ad_sets" | "quality.utm_refresh" | "wiring.recheck" | "actions.rollback" | "quality.url_tags_apply" | "quality.url_tags_undo" | "send.setup" | "send.dataset" | "send.stage" | "send.stop" | "send.restore" | null;
182426
+ engine_op: "recommendations.apply" | "ads.pause" | "ads.pause_set" | "ads.resume" | "ads.resume_set" | "adsets.budget" | "campaigns.budget" | "campaigns.pause" | "campaigns.resume" | "wiring.ad_sets" | "quality.utm_refresh" | "wiring.recheck" | "actions.rollback" | "quality.url_tags_apply" | "quality.url_tags_undo" | "send.setup" | "send.dataset" | "send.stage" | "send.stop" | "send.restore" | null;
180545
182427
  /** @description `campaign_pause` only: the line the sheet leads with — «Pausar «{campaign}» · detiene {n} anuncios · ~$X al día» (the count and the daily figure are dropped when unknown). */
180546
182428
  summary?: string;
180547
182429
  campaign?: components["schemas"]["AdsCampaignPauseSnapshot"];
@@ -181073,6 +182955,15 @@ interface components {
181073
182955
  * @enum {string|null}
181074
182956
  */
181075
182957
  reason: "not_sending" | "dataset_restricted" | "dataset_unknown" | "already_open" | null;
182958
+ /**
182959
+ * @description D81: the event the test arm should optimise for — `appointment_booked` («cita agendada») until the account reaches ~`first_payment_threshold` new patients’ first payments a week, then `first_payment`. The default `test_event` of `POST /ads/experiments`. Absent when the count could not be read.
182960
+ * @enum {string}
182961
+ */
182962
+ suggested_test_event?: "appointment_booked" | "first_payment";
182963
+ /** @description New patients’ first payments a week (one decimal; «Envío a Meta»’s «Por semana»), `null` = unknown. */
182964
+ weekly_first_payments?: number | null;
182965
+ /** @description First payments a week from which Meta optimises on the first payment (~50). */
182966
+ first_payment_threshold?: number;
181076
182967
  };
181077
182968
  AdsExperiment: {
181078
182969
  /** Format: uuid */