@acvl/mobile-components 0.0.0-stage → 0.0.1

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 (297) hide show
  1. package/README.md +146 -2
  2. package/assets/fonts/Inter_400Regular.ttf +0 -0
  3. package/assets/fonts/Inter_500Medium.ttf +0 -0
  4. package/assets/fonts/Inter_600SemiBold.ttf +0 -0
  5. package/assets/fonts/Inter_700Bold.ttf +0 -0
  6. package/assets/fonts/Inter_800ExtraBold.ttf +0 -0
  7. package/assets/fonts/Inter_900Black.ttf +0 -0
  8. package/assets/fonts/OFL.txt +93 -0
  9. package/lib/module/componentes/AccionesDelEncabezado.js +212 -0
  10. package/lib/module/componentes/AccionesDelEncabezado.js.map +1 -0
  11. package/lib/module/componentes/BotonDeAvatar.js +55 -0
  12. package/lib/module/componentes/BotonDeAvatar.js.map +1 -0
  13. package/lib/module/componentes/BotonGrande.js +105 -0
  14. package/lib/module/componentes/BotonGrande.js.map +1 -0
  15. package/lib/module/componentes/CabeceraEmpujada.js +101 -0
  16. package/lib/module/componentes/CabeceraEmpujada.js.map +1 -0
  17. package/lib/module/componentes/Chip.js +77 -0
  18. package/lib/module/componentes/Chip.js.map +1 -0
  19. package/lib/module/componentes/ContenidoDePantalla.js +56 -0
  20. package/lib/module/componentes/ContenidoDePantalla.js.map +1 -0
  21. package/lib/module/componentes/EstadoVacio.js +65 -0
  22. package/lib/module/componentes/EstadoVacio.js.map +1 -0
  23. package/lib/module/componentes/Foto.js +106 -0
  24. package/lib/module/componentes/Foto.js.map +1 -0
  25. package/lib/module/componentes/IconoDeRenglon.js +44 -0
  26. package/lib/module/componentes/IconoDeRenglon.js.map +1 -0
  27. package/lib/module/componentes/Logo.js +44 -0
  28. package/lib/module/componentes/Logo.js.map +1 -0
  29. package/lib/module/componentes/Nota.js +51 -0
  30. package/lib/module/componentes/Nota.js.map +1 -0
  31. package/lib/module/componentes/OpcionesExcluyentes.js +69 -0
  32. package/lib/module/componentes/OpcionesExcluyentes.js.map +1 -0
  33. package/lib/module/componentes/PantallaDeError.js +84 -0
  34. package/lib/module/componentes/PantallaDeError.js.map +1 -0
  35. package/lib/module/componentes/Pestanas.js +87 -0
  36. package/lib/module/componentes/Pestanas.js.map +1 -0
  37. package/lib/module/componentes/Rotulo.js +28 -0
  38. package/lib/module/componentes/Rotulo.js.map +1 -0
  39. package/lib/module/componentes/RotuloDeSeccion.js +87 -0
  40. package/lib/module/componentes/RotuloDeSeccion.js.map +1 -0
  41. package/lib/module/componentes/Tarjeta.js +47 -0
  42. package/lib/module/componentes/Tarjeta.js.map +1 -0
  43. package/lib/module/componentes/TarjetaDeDatos.js +79 -0
  44. package/lib/module/componentes/TarjetaDeDatos.js.map +1 -0
  45. package/lib/module/componentes/TiraDeFotos.js +184 -0
  46. package/lib/module/componentes/TiraDeFotos.js.map +1 -0
  47. package/lib/module/componentes/VisorDeFoto.js +171 -0
  48. package/lib/module/componentes/VisorDeFoto.js.map +1 -0
  49. package/lib/module/componentes/logos/bola-blanca.png +0 -0
  50. package/lib/module/componentes/logos/bola.png +0 -0
  51. package/lib/module/componentes/logos/logo-blanco.png +0 -0
  52. package/lib/module/componentes/logos/logo.png +0 -0
  53. package/lib/module/dialogos/HostDeDialogos.js +170 -0
  54. package/lib/module/dialogos/HostDeDialogos.js.map +1 -0
  55. package/lib/module/dialogos/dialogos.js +112 -0
  56. package/lib/module/dialogos/dialogos.js.map +1 -0
  57. package/lib/module/formularios/CampoDeSeleccion.js +47 -0
  58. package/lib/module/formularios/CampoDeSeleccion.js.map +1 -0
  59. package/lib/module/formularios/SelectorDeOpcion.js +406 -0
  60. package/lib/module/formularios/SelectorDeOpcion.js.map +1 -0
  61. package/lib/module/formularios/TextoDeAccesoControlado.js +73 -0
  62. package/lib/module/formularios/TextoDeAccesoControlado.js.map +1 -0
  63. package/lib/module/formularios/campos.js +493 -0
  64. package/lib/module/formularios/campos.js.map +1 -0
  65. package/lib/module/formularios/controlados.js +197 -0
  66. package/lib/module/formularios/controlados.js.map +1 -0
  67. package/lib/module/formularios/errores.js +91 -0
  68. package/lib/module/formularios/errores.js.map +1 -0
  69. package/lib/module/formularios/guardado.js +78 -0
  70. package/lib/module/formularios/guardado.js.map +1 -0
  71. package/lib/module/formularios/useBusqueda.js +22 -0
  72. package/lib/module/formularios/useBusqueda.js.map +1 -0
  73. package/lib/module/fotos/elegir.js +178 -0
  74. package/lib/module/fotos/elegir.js.map +1 -0
  75. package/lib/module/fotos/index.js +8 -0
  76. package/lib/module/fotos/index.js.map +1 -0
  77. package/lib/module/fotos/reducir.js +93 -0
  78. package/lib/module/fotos/reducir.js.map +1 -0
  79. package/lib/module/hoja/HojaInferior.js +108 -0
  80. package/lib/module/hoja/HojaInferior.js.map +1 -0
  81. package/lib/module/hoja/index.js +7 -0
  82. package/lib/module/hoja/index.js.map +1 -0
  83. package/lib/module/hoja/useMasDeLaFicha.js +48 -0
  84. package/lib/module/hoja/useMasDeLaFicha.js.map +1 -0
  85. package/lib/module/index.js +66 -0
  86. package/lib/module/index.js.map +1 -0
  87. package/lib/module/listas/HojaDeFiltros.js +260 -0
  88. package/lib/module/listas/HojaDeFiltros.js.map +1 -0
  89. package/lib/module/listas/ListaPaginada.js +103 -0
  90. package/lib/module/listas/ListaPaginada.js.map +1 -0
  91. package/lib/module/listas/TarjetaDeslizable.js +118 -0
  92. package/lib/module/listas/TarjetaDeslizable.js.map +1 -0
  93. package/lib/module/listas/paginado.js +11 -0
  94. package/lib/module/listas/paginado.js.map +1 -0
  95. package/lib/module/listas/piezas.js +603 -0
  96. package/lib/module/listas/piezas.js.map +1 -0
  97. package/lib/module/navegacion/BarraDePestanas.js +162 -0
  98. package/lib/module/navegacion/BarraDePestanas.js.map +1 -0
  99. package/lib/module/navegacion/BotonDeBarra.js +58 -0
  100. package/lib/module/navegacion/BotonDeBarra.js.map +1 -0
  101. package/lib/module/navegacion/encabezado.js +103 -0
  102. package/lib/module/navegacion/encabezado.js.map +1 -0
  103. package/lib/module/pantallas/PantallaDeAcceso.js +195 -0
  104. package/lib/module/pantallas/PantallaDeAcceso.js.map +1 -0
  105. package/lib/module/pantallas/PantallaDeEdicion.js +121 -0
  106. package/lib/module/pantallas/PantallaDeEdicion.js.map +1 -0
  107. package/lib/module/pantallas/PantallaDeFicha.js +94 -0
  108. package/lib/module/pantallas/PantallaDeFicha.js.map +1 -0
  109. package/lib/module/pantallas/PantallaDePerfil.js +137 -0
  110. package/lib/module/pantallas/PantallaDePerfil.js.map +1 -0
  111. package/lib/module/tema/ProveedorDeTema.js +122 -0
  112. package/lib/module/tema/ProveedorDeTema.js.map +1 -0
  113. package/lib/module/tema/estilos.js +54 -0
  114. package/lib/module/tema/estilos.js.map +1 -0
  115. package/lib/module/tema/fuentes.js +28 -0
  116. package/lib/module/tema/fuentes.js.map +1 -0
  117. package/lib/module/tema/index.js +8 -0
  118. package/lib/module/tema/index.js.map +1 -0
  119. package/lib/module/tema/paleta.js +107 -0
  120. package/lib/module/tema/paleta.js.map +1 -0
  121. package/lib/module/tema/tipografia.js +32 -0
  122. package/lib/module/tema/tipografia.js.map +1 -0
  123. package/lib/module/texto.js +48 -0
  124. package/lib/module/texto.js.map +1 -0
  125. package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts +34 -0
  126. package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts.map +1 -0
  127. package/lib/typescript/src/componentes/BotonDeAvatar.d.ts +17 -0
  128. package/lib/typescript/src/componentes/BotonDeAvatar.d.ts.map +1 -0
  129. package/lib/typescript/src/componentes/BotonGrande.d.ts +26 -0
  130. package/lib/typescript/src/componentes/BotonGrande.d.ts.map +1 -0
  131. package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts +18 -0
  132. package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts.map +1 -0
  133. package/lib/typescript/src/componentes/Chip.d.ts +14 -0
  134. package/lib/typescript/src/componentes/Chip.d.ts.map +1 -0
  135. package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts +17 -0
  136. package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts.map +1 -0
  137. package/lib/typescript/src/componentes/EstadoVacio.d.ts +17 -0
  138. package/lib/typescript/src/componentes/EstadoVacio.d.ts.map +1 -0
  139. package/lib/typescript/src/componentes/Foto.d.ts +41 -0
  140. package/lib/typescript/src/componentes/Foto.d.ts.map +1 -0
  141. package/lib/typescript/src/componentes/IconoDeRenglon.d.ts +25 -0
  142. package/lib/typescript/src/componentes/IconoDeRenglon.d.ts.map +1 -0
  143. package/lib/typescript/src/componentes/Logo.d.ts +13 -0
  144. package/lib/typescript/src/componentes/Logo.d.ts.map +1 -0
  145. package/lib/typescript/src/componentes/Nota.d.ts +7 -0
  146. package/lib/typescript/src/componentes/Nota.d.ts.map +1 -0
  147. package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts +14 -0
  148. package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts.map +1 -0
  149. package/lib/typescript/src/componentes/PantallaDeError.d.ts +23 -0
  150. package/lib/typescript/src/componentes/PantallaDeError.d.ts.map +1 -0
  151. package/lib/typescript/src/componentes/Pestanas.d.ts +28 -0
  152. package/lib/typescript/src/componentes/Pestanas.d.ts.map +1 -0
  153. package/lib/typescript/src/componentes/Rotulo.d.ts +6 -0
  154. package/lib/typescript/src/componentes/Rotulo.d.ts.map +1 -0
  155. package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts +20 -0
  156. package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts.map +1 -0
  157. package/lib/typescript/src/componentes/Tarjeta.d.ts +15 -0
  158. package/lib/typescript/src/componentes/Tarjeta.d.ts.map +1 -0
  159. package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts +13 -0
  160. package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts.map +1 -0
  161. package/lib/typescript/src/componentes/TiraDeFotos.d.ts +36 -0
  162. package/lib/typescript/src/componentes/TiraDeFotos.d.ts.map +1 -0
  163. package/lib/typescript/src/componentes/VisorDeFoto.d.ts +23 -0
  164. package/lib/typescript/src/componentes/VisorDeFoto.d.ts.map +1 -0
  165. package/lib/typescript/src/dialogos/HostDeDialogos.d.ts +14 -0
  166. package/lib/typescript/src/dialogos/HostDeDialogos.d.ts.map +1 -0
  167. package/lib/typescript/src/dialogos/dialogos.d.ts +65 -0
  168. package/lib/typescript/src/dialogos/dialogos.d.ts.map +1 -0
  169. package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts +22 -0
  170. package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts.map +1 -0
  171. package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts +59 -0
  172. package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -0
  173. package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts +18 -0
  174. package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts.map +1 -0
  175. package/lib/typescript/src/formularios/campos.d.ts +107 -0
  176. package/lib/typescript/src/formularios/campos.d.ts.map +1 -0
  177. package/lib/typescript/src/formularios/controlados.d.ts +29 -0
  178. package/lib/typescript/src/formularios/controlados.d.ts.map +1 -0
  179. package/lib/typescript/src/formularios/errores.d.ts +36 -0
  180. package/lib/typescript/src/formularios/errores.d.ts.map +1 -0
  181. package/lib/typescript/src/formularios/guardado.d.ts +36 -0
  182. package/lib/typescript/src/formularios/guardado.d.ts.map +1 -0
  183. package/lib/typescript/src/formularios/useBusqueda.d.ts +7 -0
  184. package/lib/typescript/src/formularios/useBusqueda.d.ts.map +1 -0
  185. package/lib/typescript/src/fotos/elegir.d.ts +58 -0
  186. package/lib/typescript/src/fotos/elegir.d.ts.map +1 -0
  187. package/lib/typescript/src/fotos/index.d.ts +3 -0
  188. package/lib/typescript/src/fotos/index.d.ts.map +1 -0
  189. package/lib/typescript/src/fotos/reducir.d.ts +32 -0
  190. package/lib/typescript/src/fotos/reducir.d.ts.map +1 -0
  191. package/lib/typescript/src/hoja/HojaInferior.d.ts +21 -0
  192. package/lib/typescript/src/hoja/HojaInferior.d.ts.map +1 -0
  193. package/lib/typescript/src/hoja/index.d.ts +3 -0
  194. package/lib/typescript/src/hoja/index.d.ts.map +1 -0
  195. package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts +27 -0
  196. package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts.map +1 -0
  197. package/lib/typescript/src/index.d.ts +45 -0
  198. package/lib/typescript/src/index.d.ts.map +1 -0
  199. package/lib/typescript/src/listas/HojaDeFiltros.d.ts +60 -0
  200. package/lib/typescript/src/listas/HojaDeFiltros.d.ts.map +1 -0
  201. package/lib/typescript/src/listas/ListaPaginada.d.ts +39 -0
  202. package/lib/typescript/src/listas/ListaPaginada.d.ts.map +1 -0
  203. package/lib/typescript/src/listas/TarjetaDeslizable.d.ts +33 -0
  204. package/lib/typescript/src/listas/TarjetaDeslizable.d.ts.map +1 -0
  205. package/lib/typescript/src/listas/paginado.d.ts +16 -0
  206. package/lib/typescript/src/listas/paginado.d.ts.map +1 -0
  207. package/lib/typescript/src/listas/piezas.d.ts +135 -0
  208. package/lib/typescript/src/listas/piezas.d.ts.map +1 -0
  209. package/lib/typescript/src/navegacion/BarraDePestanas.d.ts +57 -0
  210. package/lib/typescript/src/navegacion/BarraDePestanas.d.ts.map +1 -0
  211. package/lib/typescript/src/navegacion/BotonDeBarra.d.ts +15 -0
  212. package/lib/typescript/src/navegacion/BotonDeBarra.d.ts.map +1 -0
  213. package/lib/typescript/src/navegacion/encabezado.d.ts +44 -0
  214. package/lib/typescript/src/navegacion/encabezado.d.ts.map +1 -0
  215. package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts +32 -0
  216. package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts.map +1 -0
  217. package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts +38 -0
  218. package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts.map +1 -0
  219. package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts +35 -0
  220. package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts.map +1 -0
  221. package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts +19 -0
  222. package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts.map +1 -0
  223. package/lib/typescript/src/tema/ProveedorDeTema.d.ts +32 -0
  224. package/lib/typescript/src/tema/ProveedorDeTema.d.ts.map +1 -0
  225. package/lib/typescript/src/tema/estilos.d.ts +31 -0
  226. package/lib/typescript/src/tema/estilos.d.ts.map +1 -0
  227. package/lib/typescript/src/tema/fuentes.d.ts +20 -0
  228. package/lib/typescript/src/tema/fuentes.d.ts.map +1 -0
  229. package/lib/typescript/src/tema/index.d.ts +6 -0
  230. package/lib/typescript/src/tema/index.d.ts.map +1 -0
  231. package/lib/typescript/src/tema/paleta.d.ts +86 -0
  232. package/lib/typescript/src/tema/paleta.d.ts.map +1 -0
  233. package/lib/typescript/src/tema/tipografia.d.ts +29 -0
  234. package/lib/typescript/src/tema/tipografia.d.ts.map +1 -0
  235. package/lib/typescript/src/texto.d.ts +16 -0
  236. package/lib/typescript/src/texto.d.ts.map +1 -0
  237. package/package.json +135 -5
  238. package/src/componentes/AccionesDelEncabezado.tsx +247 -0
  239. package/src/componentes/BotonDeAvatar.tsx +59 -0
  240. package/src/componentes/BotonGrande.tsx +100 -0
  241. package/src/componentes/CabeceraEmpujada.tsx +69 -0
  242. package/src/componentes/Chip.tsx +60 -0
  243. package/src/componentes/ContenidoDePantalla.tsx +53 -0
  244. package/src/componentes/EstadoVacio.tsx +45 -0
  245. package/src/componentes/Foto.tsx +92 -0
  246. package/src/componentes/IconoDeRenglon.tsx +42 -0
  247. package/src/componentes/Logo.tsx +34 -0
  248. package/src/componentes/Nota.tsx +37 -0
  249. package/src/componentes/OpcionesExcluyentes.tsx +62 -0
  250. package/src/componentes/PantallaDeError.tsx +76 -0
  251. package/src/componentes/Pestanas.tsx +86 -0
  252. package/src/componentes/Rotulo.tsx +21 -0
  253. package/src/componentes/RotuloDeSeccion.tsx +71 -0
  254. package/src/componentes/Tarjeta.tsx +44 -0
  255. package/src/componentes/TarjetaDeDatos.tsx +62 -0
  256. package/src/componentes/TiraDeFotos.tsx +189 -0
  257. package/src/componentes/VisorDeFoto.tsx +181 -0
  258. package/src/componentes/logos/bola-blanca.png +0 -0
  259. package/src/componentes/logos/bola.png +0 -0
  260. package/src/componentes/logos/logo-blanco.png +0 -0
  261. package/src/componentes/logos/logo.png +0 -0
  262. package/src/dialogos/HostDeDialogos.tsx +172 -0
  263. package/src/dialogos/dialogos.ts +148 -0
  264. package/src/formularios/CampoDeSeleccion.tsx +46 -0
  265. package/src/formularios/SelectorDeOpcion.tsx +407 -0
  266. package/src/formularios/TextoDeAccesoControlado.tsx +72 -0
  267. package/src/formularios/campos.tsx +483 -0
  268. package/src/formularios/controlados.tsx +198 -0
  269. package/src/formularios/errores.ts +105 -0
  270. package/src/formularios/guardado.ts +76 -0
  271. package/src/formularios/useBusqueda.ts +19 -0
  272. package/src/fotos/elegir.ts +194 -0
  273. package/src/fotos/index.ts +15 -0
  274. package/src/fotos/reducir.ts +87 -0
  275. package/src/hoja/HojaInferior.tsx +102 -0
  276. package/src/hoja/index.ts +4 -0
  277. package/src/hoja/useMasDeLaFicha.tsx +63 -0
  278. package/src/index.ts +143 -0
  279. package/src/listas/HojaDeFiltros.tsx +261 -0
  280. package/src/listas/ListaPaginada.tsx +117 -0
  281. package/src/listas/TarjetaDeslizable.tsx +126 -0
  282. package/src/listas/paginado.ts +15 -0
  283. package/src/listas/piezas.tsx +594 -0
  284. package/src/navegacion/BarraDePestanas.tsx +198 -0
  285. package/src/navegacion/BotonDeBarra.tsx +61 -0
  286. package/src/navegacion/encabezado.tsx +88 -0
  287. package/src/pantallas/PantallaDeAcceso.tsx +184 -0
  288. package/src/pantallas/PantallaDeEdicion.tsx +116 -0
  289. package/src/pantallas/PantallaDeFicha.tsx +115 -0
  290. package/src/pantallas/PantallaDePerfil.tsx +126 -0
  291. package/src/tema/ProveedorDeTema.tsx +150 -0
  292. package/src/tema/estilos.ts +51 -0
  293. package/src/tema/fuentes.ts +20 -0
  294. package/src/tema/index.ts +10 -0
  295. package/src/tema/paleta.ts +147 -0
  296. package/src/tema/tipografia.ts +29 -0
  297. package/src/texto.tsx +44 -0
@@ -0,0 +1,189 @@
1
+ import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
2
+ import React from "react";
3
+ import { Pressable, ScrollView, View } from "react-native";
4
+ import { crearEstilos, tipografia, usePaleta } from "../tema";
5
+ import { Text } from "../texto";
6
+ import Foto, { type FotoAVer } from "./Foto";
7
+ import VisorDeFoto from "./VisorDeFoto";
8
+
9
+ /** Una miniatura de la tira: qué se pinta, qué abre el visor y qué se puede hacer con ella. */
10
+ export interface FotoDeLaTira {
11
+ llave: string;
12
+ /** Un archivo que no es foto (un PDF) se ve con su icono y no entra al visor. */
13
+ esFoto: boolean;
14
+ miniatura: FotoAVer;
15
+ /** La foto completa, si la miniatura es una previa chica del servidor. Por omisión, ésa. */
16
+ completa?: FotoAVer;
17
+ /** El nombre del archivo: se ve en un documento que no es foto. */
18
+ nombre?: string;
19
+ /** Una nube en la esquina: verde si ya está en el servidor, ámbar si sigue en el teléfono. */
20
+ marca?: "enviada" | "sinSubir";
21
+ /** Con esto lleva su «×» para quitarla. */
22
+ onQuitar?: () => void;
23
+ /** Con esto, el visor lleva «descargar» en ella. Rechaza si no se pudo. */
24
+ onDescargar?: () => Promise<void>;
25
+ /** Lo que dice el lector de pantalla, si no basta con «Ver la foto». */
26
+ etiqueta?: string;
27
+ }
28
+
29
+ interface Props {
30
+ fotos: FotoDeLaTira[];
31
+ /** El lado de cada miniatura. */
32
+ lado: number;
33
+ /** En cuadrícula (una hoja, donde hay lugar) o en un renglón que se desliza a los lados. */
34
+ cuadricula?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Unas fotos en miniatura; tocar una abre el visor en ésa y se desliza entre las demás, **en el
39
+ * mismo orden** que las miniaturas. Quien la usa arma las piezas con lo suyo (lo del servidor
40
+ * primero y lo del teléfono después, por ejemplo) y les pone su marca y sus acciones.
41
+ */
42
+ const TiraDeFotos = ({ fotos: piezas, lado, cuadricula = false }: Props) => {
43
+ const styles = useStyles();
44
+ const [viendo, setViendo] = React.useState<number | null>(null);
45
+ if (piezas.length === 0) return null;
46
+
47
+ const fotos = piezas.filter((p) => p.esFoto);
48
+ const carrusel = fotos.map((p) => p.completa ?? p.miniatura);
49
+ const descargables = fotos.some((p) => p.onDescargar);
50
+ const tamano = { width: lado, height: lado };
51
+
52
+ const miniaturas = piezas.map((p) => (
53
+ <View key={p.llave}>
54
+ <Pressable
55
+ disabled={!p.esFoto}
56
+ onPress={() => setViendo(fotos.indexOf(p))}
57
+ accessibilityRole={p.esFoto ? "imagebutton" : undefined}
58
+ accessibilityLabel={
59
+ p.etiqueta ?? (p.esFoto ? "Ver la foto" : `Archivo ${p.nombre ?? ""}`.trim())
60
+ }
61
+ >
62
+ {p.esFoto ? (
63
+ <Foto
64
+ foto={p.miniatura}
65
+ style={[styles.miniatura, tamano]}
66
+ resizeMethod="resize"
67
+ />
68
+ ) : (
69
+ <Documento lado={lado} nombre={p.nombre} />
70
+ )}
71
+ {p.marca === "enviada" ? (
72
+ <View style={[styles.marca, styles.marcaEnviada]}>
73
+ <MaterialCommunityIcons name="cloud-check" size={16} color="#fff" />
74
+ </View>
75
+ ) : null}
76
+ {p.marca === "sinSubir" ? (
77
+ <View style={[styles.marca, styles.marcaSinSubir]}>
78
+ <MaterialCommunityIcons name="cloud-clock-outline" size={16} color="#fff" />
79
+ </View>
80
+ ) : null}
81
+ </Pressable>
82
+ {p.onQuitar ? (
83
+ <Pressable
84
+ onPress={p.onQuitar}
85
+ hitSlop={10}
86
+ accessibilityRole="button"
87
+ accessibilityLabel={p.esFoto ? "Quitar esta foto" : "Quitar este archivo"}
88
+ style={styles.quitar}
89
+ >
90
+ <MaterialCommunityIcons name="close" size={24} color="#fff" />
91
+ </Pressable>
92
+ ) : null}
93
+ </View>
94
+ ));
95
+
96
+ return (
97
+ <>
98
+ {cuadricula ? (
99
+ <View style={styles.cuadricula}>{miniaturas}</View>
100
+ ) : (
101
+ // Un solo renglón que se desliza: con varias fotos no se parte en filas.
102
+ <ScrollView
103
+ horizontal
104
+ showsHorizontalScrollIndicator={false}
105
+ contentContainerStyle={styles.renglon}
106
+ >
107
+ {miniaturas}
108
+ </ScrollView>
109
+ )}
110
+ <VisorDeFoto
111
+ fotos={carrusel}
112
+ inicio={viendo}
113
+ onCerrar={() => setViendo(null)}
114
+ onDescargar={
115
+ descargables
116
+ ? async (i) => {
117
+ const descargar = fotos[i]?.onDescargar;
118
+ if (!descargar) throw new Error("Esta foto no se puede descargar.");
119
+ await descargar();
120
+ }
121
+ : undefined
122
+ }
123
+ />
124
+ </>
125
+ );
126
+ };
127
+
128
+ /** La miniatura de un archivo que no es foto (un PDF): un icono y, si cabe, su nombre. */
129
+ function Documento({ lado, nombre }: { lado: number; nombre?: string }) {
130
+ const c = usePaleta();
131
+ const styles = useStyles();
132
+ const conNombre = Boolean(nombre) && lado >= 100;
133
+ return (
134
+ <View style={[styles.miniatura, styles.documento, { width: lado, height: lado }]}>
135
+ <MaterialCommunityIcons
136
+ name="file-document-outline"
137
+ size={Math.round(lado * 0.3)}
138
+ color={c.secondary}
139
+ />
140
+ {conNombre ? (
141
+ // Dentro de un cuadro fijo: el nombre largo de un PDF tiene que cortarse.
142
+ <Text numberOfLines={2} style={styles.documentoNombre}>
143
+ {nombre}
144
+ </Text>
145
+ ) : null}
146
+ </View>
147
+ );
148
+ }
149
+
150
+ // Las marcas y la «×» van encima de la foto: sus colores son fijos para leerse sobre cualquier foto.
151
+ const useStyles = crearEstilos((c) => ({
152
+ renglon: { gap: 8 },
153
+ cuadricula: { flexDirection: "row", flexWrap: "wrap", gap: 12 },
154
+ miniatura: { borderRadius: 10, backgroundColor: c.secondaryLighter },
155
+ documento: { alignItems: "center", justifyContent: "center", padding: 6, gap: 2 },
156
+ documentoNombre: {
157
+ fontSize: tipografia.etiqueta,
158
+ color: c.secondaryDark,
159
+ textAlign: "center",
160
+ },
161
+ marca: {
162
+ position: "absolute",
163
+ bottom: 4,
164
+ right: 4,
165
+ width: 24,
166
+ height: 24,
167
+ borderRadius: 12,
168
+ alignItems: "center",
169
+ justifyContent: "center",
170
+ },
171
+ marcaEnviada: { backgroundColor: "#2e7d32" },
172
+ // La nube ámbar sobre una foto que sigue en el teléfono sin subir.
173
+ marcaSinSubir: { backgroundColor: "#b26a00" },
174
+ quitar: {
175
+ position: "absolute",
176
+ top: 6,
177
+ right: 6,
178
+ width: 40,
179
+ height: 40,
180
+ borderRadius: 20,
181
+ backgroundColor: c.primaryDark,
182
+ alignItems: "center",
183
+ justifyContent: "center",
184
+ borderWidth: 2,
185
+ borderColor: "#fff",
186
+ },
187
+ }));
188
+
189
+ export default TiraDeFotos;
@@ -0,0 +1,181 @@
1
+ import { renglon, tipografia } from "../tema";
2
+ import { Text } from "../texto";
3
+ import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
4
+ import React from "react";
5
+ import {
6
+ ActivityIndicator,
7
+ FlatList,
8
+ Modal,
9
+ Pressable,
10
+ StyleSheet,
11
+ useWindowDimensions,
12
+ View,
13
+ } from "react-native";
14
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
15
+ import Foto, { type FotoAVer } from "./Foto";
16
+
17
+ export type { FotoAVer };
18
+
19
+ interface Props {
20
+ /** Las fotos, en el orden de sus miniaturas. Los archivos que no son foto no entran. */
21
+ fotos: FotoAVer[];
22
+ /** La que se tocó; `null` cierra el visor. */
23
+ inicio: number | null;
24
+ onCerrar: () => void;
25
+ /**
26
+ * Con esto, la barra lleva «descargar» para la foto que se ve.
27
+ * Recibe su lugar en `fotos`; rechaza si no se pudo.
28
+ */
29
+ onDescargar?: (indice: number) => Promise<void>;
30
+ }
31
+
32
+ /** Cómo va la descarga de la foto que se ve: el icono de la barra lo dice. */
33
+ type Descarga = "nada" | "bajando" | "lista" | "fallo";
34
+ const ICONO_DE_DESCARGA = {
35
+ nada: "download",
36
+ lista: "check",
37
+ fallo: "alert-circle-outline",
38
+ } as const;
39
+
40
+ const llave = (foto: FotoAVer, i: number) => ("ruta" in foto ? foto.ruta : foto.uri) + i;
41
+
42
+ /**
43
+ * Unas fotos a pantalla completa, sobre fondo negro: se abre en la que se tocó y se pasa a la
44
+ * siguiente o la anterior deslizando a los lados, con «2 de 4» arriba. Mezcla las subidas y las del
45
+ * teléfono. Se cierra con la X o con atrás. Lo abre `TiraDeFotos`.
46
+ */
47
+ const VisorDeFoto = ({ fotos, inicio, onCerrar, onDescargar }: Props) => (
48
+ <Modal
49
+ visible={inicio !== null}
50
+ transparent
51
+ animationType="fade"
52
+ statusBarTranslucent
53
+ onRequestClose={onCerrar}
54
+ >
55
+ {/* Con `key`, cada apertura nace en la foto que se tocó. */}
56
+ {inicio !== null ? (
57
+ <Carrusel
58
+ key={inicio}
59
+ fotos={fotos}
60
+ inicio={inicio}
61
+ onCerrar={onCerrar}
62
+ onDescargar={onDescargar}
63
+ />
64
+ ) : null}
65
+ </Modal>
66
+ );
67
+
68
+ function Carrusel({
69
+ fotos,
70
+ inicio,
71
+ onCerrar,
72
+ onDescargar,
73
+ }: {
74
+ fotos: FotoAVer[];
75
+ inicio: number;
76
+ onCerrar: () => void;
77
+ onDescargar?: (indice: number) => Promise<void>;
78
+ }) {
79
+ const insets = useSafeAreaInsets();
80
+ const { width, height } = useWindowDimensions();
81
+ const primera = Math.min(inicio, Math.max(0, fotos.length - 1));
82
+ const [actual, setActual] = React.useState(primera);
83
+ const [descarga, setDescarga] = React.useState<Descarga>("nada");
84
+
85
+ // Lo que dice el icono es de la foto en la que estaba: al pasar a otra, vuelve a «descargar».
86
+ const pasarA = (indice: number) => {
87
+ if (indice !== actual) setDescarga("nada");
88
+ setActual(indice);
89
+ };
90
+
91
+ const descargar = async () => {
92
+ if (!onDescargar || descarga === "bajando") return;
93
+ setDescarga("bajando");
94
+ try {
95
+ await onDescargar(actual);
96
+ setDescarga("lista");
97
+ } catch {
98
+ setDescarga("fallo");
99
+ }
100
+ };
101
+
102
+ return (
103
+ <View style={styles.fondo}>
104
+ <FlatList
105
+ data={fotos}
106
+ keyExtractor={llave}
107
+ horizontal
108
+ pagingEnabled
109
+ showsHorizontalScrollIndicator={false}
110
+ initialScrollIndex={primera}
111
+ getItemLayout={(_, i) => ({ length: width, offset: width * i, index: i })}
112
+ onMomentumScrollEnd={(e) =>
113
+ pasarA(Math.round(e.nativeEvent.contentOffset.x / width))
114
+ }
115
+ renderItem={({ item }) => (
116
+ <View style={{ width, height }}>
117
+ <Foto foto={item} style={styles.foto} resizeMode="contain" />
118
+ </View>
119
+ )}
120
+ />
121
+
122
+ <View style={[styles.barra, { top: insets.top + 12 }]}>
123
+ {fotos.length > 1 ? (
124
+ <Text style={styles.cuenta}>
125
+ {actual + 1} de {fotos.length}
126
+ </Text>
127
+ ) : (
128
+ <View />
129
+ )}
130
+ <View style={styles.botones}>
131
+ {onDescargar ? (
132
+ <Pressable
133
+ onPress={descargar}
134
+ hitSlop={12}
135
+ accessibilityRole="button"
136
+ accessibilityLabel={
137
+ descarga === "lista"
138
+ ? "Guardada en la galería"
139
+ : descarga === "fallo"
140
+ ? "No se pudo guardar; intentar de nuevo"
141
+ : "Guardar en la galería"
142
+ }
143
+ accessibilityState={{ busy: descarga === "bajando" }}
144
+ >
145
+ {descarga === "bajando" ? (
146
+ <ActivityIndicator size={32} color="#fff" />
147
+ ) : (
148
+ <MaterialCommunityIcons
149
+ name={ICONO_DE_DESCARGA[descarga]}
150
+ size={32}
151
+ color="#fff"
152
+ />
153
+ )}
154
+ </Pressable>
155
+ ) : null}
156
+ <Pressable onPress={onCerrar} hitSlop={12} accessibilityLabel="Cerrar">
157
+ <MaterialCommunityIcons name="close" size={32} color="#fff" />
158
+ </Pressable>
159
+ </View>
160
+ </View>
161
+ </View>
162
+ );
163
+ }
164
+
165
+ // Sobre la foto: negro con texto blanco, en claro y en oscuro.
166
+ const styles = StyleSheet.create({
167
+ fondo: { flex: 1, backgroundColor: "rgba(0,0,0,0.95)" },
168
+ foto: { width: "100%", height: "100%" },
169
+ barra: {
170
+ position: "absolute",
171
+ left: 16,
172
+ right: 16,
173
+ flexDirection: "row",
174
+ alignItems: "center",
175
+ justifyContent: "space-between",
176
+ },
177
+ botones: { flexDirection: "row", alignItems: "center", gap: 28 },
178
+ cuenta: { ...renglon(tipografia.cuerpo), color: "#fff", fontWeight: "800" },
179
+ });
180
+
181
+ export default VisorDeFoto;
Binary file
Binary file
@@ -0,0 +1,172 @@
1
+ import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
2
+ import BotonGrande from "../componentes/BotonGrande";
3
+ import { Text } from "../texto";
4
+ import {
5
+ dialogoActual,
6
+ responderDialogo,
7
+ suscribirDialogos,
8
+ type BotonDeDialogo,
9
+ } from "./dialogos";
10
+ import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
11
+ import React from "react";
12
+ import { Modal, Pressable, ScrollView, useWindowDimensions, View } from "react-native";
13
+
14
+ /**
15
+ * Pinta los diálogos que abre `mostrarDialogo` (`dialogos.ts`), de uno en uno. Se monta una
16
+ * sola vez, en el layout raíz de la app.
17
+ *
18
+ * Es el diálogo de la casa —el `ConfirmDialog` de las revisiones de wms— con la letra grande de las apps:
19
+ * una tarjeta blanca sobre fondo oscuro, el icono arriba, título y mensaje sin recortar, y los
20
+ * botones **apilados a lo ancho**: lado a lado no caben con la letra al 180%. «Cancelar» va como
21
+ * texto discreto al pie. Atrás y el toque fuera cierran como cancelar, salvo que el diálogo diga
22
+ * que no es cancelable.
23
+ */
24
+ const HostDeDialogos = () => {
25
+ const c = usePaleta();
26
+ const styles = useStyles();
27
+ const dialogo = React.useSyncExternalStore(suscribirDialogos, dialogoActual);
28
+ const { height } = useWindowDimensions();
29
+
30
+ if (!dialogo) return null;
31
+
32
+ const cancelable = dialogo.cancelable ?? true;
33
+ const cerrar = () => {
34
+ if (cancelable) responderDialogo(dialogo, undefined);
35
+ };
36
+ const acciones = dialogo.botones.filter((b) => b.estilo !== "cancelar");
37
+ const cancelar = dialogo.botones.find((b) => b.estilo === "cancelar");
38
+
39
+ return (
40
+ <Modal
41
+ visible
42
+ transparent
43
+ animationType="fade"
44
+ statusBarTranslucent
45
+ navigationBarTranslucent
46
+ onRequestClose={cerrar}
47
+ >
48
+ <Pressable style={styles.fondo} onPress={cerrar} accessibilityLabel="Cerrar">
49
+ {/* El `Pressable` vacío de la tarjeta se queda con el toque: tocar adentro no cierra. */}
50
+ <Pressable
51
+ style={[styles.tarjeta, { maxHeight: height * 0.85 }]}
52
+ onPress={() => {}}
53
+ >
54
+ <ScrollView
55
+ contentContainerStyle={styles.contenido}
56
+ showsVerticalScrollIndicator={false}
57
+ >
58
+ {dialogo.icono ? (
59
+ <View style={styles.icono}>
60
+ <MaterialCommunityIcons
61
+ name={dialogo.icono}
62
+ size={34}
63
+ color={c.primary}
64
+ />
65
+ </View>
66
+ ) : null}
67
+ <Text style={styles.titulo}>{dialogo.titulo}</Text>
68
+ {dialogo.mensaje ? (
69
+ <Text style={styles.mensaje}>{dialogo.mensaje}</Text>
70
+ ) : null}
71
+
72
+ <View style={styles.botones}>
73
+ {acciones.map((boton, i) => (
74
+ <Boton
75
+ key={`${boton.texto}-${i}`}
76
+ boton={boton}
77
+ onPress={() => responderDialogo(dialogo, boton.valor)}
78
+ />
79
+ ))}
80
+ {cancelar ? (
81
+ <Pressable
82
+ onPress={() => responderDialogo(dialogo, cancelar.valor)}
83
+ hitSlop={8}
84
+ accessibilityRole="button"
85
+ style={({ pressed }) => [
86
+ styles.cancelar,
87
+ pressed && { opacity: 0.6 },
88
+ ]}
89
+ >
90
+ <Text style={styles.cancelarTexto}>{cancelar.texto}</Text>
91
+ </Pressable>
92
+ ) : null}
93
+ </View>
94
+ </ScrollView>
95
+ </Pressable>
96
+ </Pressable>
97
+ </Modal>
98
+ );
99
+ };
100
+
101
+ function Boton({ boton, onPress }: { boton: BotonDeDialogo<unknown>; onPress: () => void }) {
102
+ const estilo = boton.estilo ?? "principal";
103
+ return (
104
+ <BotonGrande
105
+ icono={boton.icono}
106
+ modo={
107
+ estilo === "secundario"
108
+ ? "contorno"
109
+ : estilo === "destructivo"
110
+ ? "destructivo"
111
+ : "relleno"
112
+ }
113
+ onPress={onPress}
114
+ >
115
+ {boton.texto}
116
+ </BotonGrande>
117
+ );
118
+ }
119
+
120
+ const useStyles = crearEstilos((c) => ({
121
+ fondo: {
122
+ flex: 1,
123
+ backgroundColor: c.velo,
124
+ justifyContent: "center",
125
+ padding: 20,
126
+ },
127
+ tarjeta: {
128
+ width: "100%",
129
+ maxWidth: 480,
130
+ alignSelf: "center",
131
+ backgroundColor: c.superficie,
132
+ borderRadius: 20,
133
+ overflow: "hidden",
134
+ elevation: 12,
135
+ shadowColor: c.sombra,
136
+ shadowOffset: { width: 0, height: 10 },
137
+ shadowOpacity: 0.25,
138
+ shadowRadius: 24,
139
+ },
140
+ contenido: { padding: 22, gap: 10 },
141
+ icono: {
142
+ alignSelf: "center",
143
+ width: 60,
144
+ height: 60,
145
+ borderRadius: 30,
146
+ backgroundColor: c.primaryWash,
147
+ alignItems: "center",
148
+ justifyContent: "center",
149
+ marginBottom: 4,
150
+ },
151
+ titulo: {
152
+ ...renglon(tipografia.titulo),
153
+ fontWeight: "900",
154
+ color: c.texto,
155
+ textAlign: "center",
156
+ },
157
+ mensaje: {
158
+ ...renglon(tipografia.cuerpo),
159
+ color: c.secondaryDark,
160
+ textAlign: "center",
161
+ },
162
+ botones: { gap: 10, marginTop: 12 },
163
+ cancelar: { alignSelf: "center", paddingVertical: 12, paddingHorizontal: 20 },
164
+ cancelarTexto: {
165
+ ...renglon(tipografia.cuerpo),
166
+ fontWeight: "700",
167
+ color: c.secondary,
168
+ textAlign: "center",
169
+ },
170
+ }));
171
+
172
+ export default HostDeDialogos;
@@ -0,0 +1,148 @@
1
+ // Los diálogos de las apps, con su diseño y no el `Alert` del sistema. Esto es la API: la abren las
2
+ // pantallas y también código que no es componente (una cola de envíos, la ubicación, la
3
+ // navegación). Los pinta `HostDeDialogos`, montado una vez en el layout raíz.
4
+ import type MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
5
+ import type React from "react";
6
+
7
+ type NombreDeIcono = React.ComponentProps<typeof MaterialCommunityIcons>["name"];
8
+
9
+ /**
10
+ * - `principal`: la acción esperada, en rojo.
11
+ * - `secundario`: otra opción válida, en contorno.
12
+ * - `destructivo`: borra o deshace algo (Deshacer, Descartar, Cerrar sesión), en rojo oscuro.
13
+ * - `cancelar`: no hacer nada, como texto discreto al pie.
14
+ */
15
+ export type EstiloDeBoton = "principal" | "secundario" | "destructivo" | "cancelar";
16
+
17
+ export interface BotonDeDialogo<V> {
18
+ texto: string;
19
+ estilo?: EstiloDeBoton;
20
+ icono?: NombreDeIcono;
21
+ /** Lo que resuelve la promesa de `mostrarDialogo` al tocarlo. */
22
+ valor: V;
23
+ }
24
+
25
+ export interface Dialogo<V> {
26
+ titulo: string;
27
+ mensaje?: string;
28
+ icono?: NombreDeIcono;
29
+ botones: BotonDeDialogo<V>[];
30
+ /**
31
+ * Si el botón atrás de Android y el toque fuera de la tarjeta lo cierran (resolviendo
32
+ * `undefined`, como cancelar). Por omisión, sí.
33
+ */
34
+ cancelable?: boolean;
35
+ }
36
+
37
+ interface EnEspera {
38
+ dialogo: Dialogo<unknown>;
39
+ responder: (valor: unknown) => void;
40
+ }
41
+
42
+ /**
43
+ * La fila de diálogos. Vive en `globalThis` para que Fast Refresh, al re-evaluar este módulo, no
44
+ * deje al host escuchando una fila vieja con promesas que nunca se resuelven.
45
+ */
46
+ const g = globalThis as typeof globalThis & {
47
+ __dialogos?: { fila: EnEspera[]; oyentes: Set<() => void> };
48
+ };
49
+ const estado = (g.__dialogos ??= { fila: [], oyentes: new Set() });
50
+
51
+ const avisarCambio = () => estado.oyentes.forEach((oyente) => oyente());
52
+
53
+ /**
54
+ * Abre un diálogo y espera la respuesta: el `valor` del botón tocado, o `undefined` si se cerró
55
+ * sin elegir (atrás, toque fuera). Si ya hay uno abierto, éste espera su turno: se muestran de uno
56
+ * en uno, en el orden en que llegaron.
57
+ */
58
+ export function mostrarDialogo<V>(dialogo: Dialogo<V>): Promise<V | undefined> {
59
+ return new Promise((resolver) => {
60
+ estado.fila.push({
61
+ dialogo: dialogo as Dialogo<unknown>,
62
+ responder: resolver as (valor: unknown) => void,
63
+ });
64
+ avisarCambio();
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Una pregunta de sí o no. Resuelve `true` sólo si se tocó el botón de confirmar; cancelar,
70
+ * atrás y el toque fuera dan `false`.
71
+ */
72
+ export async function confirmar({
73
+ titulo,
74
+ mensaje,
75
+ textoConfirmar,
76
+ textoCancelar = "Cancelar",
77
+ destructivo = false,
78
+ icono,
79
+ }: {
80
+ titulo: string;
81
+ mensaje?: string;
82
+ textoConfirmar: string;
83
+ textoCancelar?: string;
84
+ destructivo?: boolean;
85
+ icono?: NombreDeIcono;
86
+ }): Promise<boolean> {
87
+ const respuesta = await mostrarDialogo({
88
+ titulo,
89
+ mensaje,
90
+ icono: icono ?? (destructivo ? "alert-circle-outline" : "help-circle-outline"),
91
+ botones: [
92
+ {
93
+ texto: textoConfirmar,
94
+ estilo: destructivo ? "destructivo" : "principal",
95
+ valor: true,
96
+ },
97
+ { texto: textoCancelar, estilo: "cancelar", valor: false },
98
+ ],
99
+ });
100
+ return respuesta === true;
101
+ }
102
+
103
+ /** Un aviso con un solo botón. Resuelve al cerrarse. */
104
+ export async function avisar({
105
+ titulo,
106
+ mensaje,
107
+ icono = "information-outline",
108
+ textoBoton = "Entendido",
109
+ cancelable = true,
110
+ }: {
111
+ titulo: string;
112
+ mensaje?: string;
113
+ icono?: NombreDeIcono;
114
+ textoBoton?: string;
115
+ cancelable?: boolean;
116
+ }): Promise<void> {
117
+ await mostrarDialogo({
118
+ titulo,
119
+ mensaje,
120
+ icono,
121
+ cancelable,
122
+ botones: [{ texto: textoBoton, estilo: "principal", valor: true }],
123
+ });
124
+ }
125
+
126
+ // ==============================|| PARA EL HOST ||============================== //
127
+
128
+ /** El que se está mostrando, o `null`. */
129
+ export const dialogoActual = (): Dialogo<unknown> | null => estado.fila[0]?.dialogo ?? null;
130
+
131
+ export function suscribirDialogos(oyente: () => void): () => void {
132
+ estado.oyentes.add(oyente);
133
+ return () => {
134
+ estado.oyentes.delete(oyente);
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Cierra `dialogo` con `valor` y pasa al siguiente de la fila. Sólo si `dialogo` sigue siendo el
140
+ * actual: el segundo toque de un doble toque llega con el diálogo que ya se contestó, y sin esta
141
+ * comparación contestaba también el siguiente de la fila sin que el operador lo viera.
142
+ */
143
+ export function responderDialogo(dialogo: Dialogo<unknown>, valor: unknown): void {
144
+ if (estado.fila[0]?.dialogo !== dialogo) return;
145
+ const actual = estado.fila.shift();
146
+ actual?.responder(valor);
147
+ avisarCambio();
148
+ }